@noopolis/daimon 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/README.md +6 -0
  2. package/dist/contracts/attentionContract.d.ts +25 -0
  3. package/dist/contracts/attentionContract.d.ts.map +1 -1
  4. package/dist/contracts/attentionContract.js +8 -1
  5. package/dist/contracts/grokBrokerTaskClock.d.ts +18 -0
  6. package/dist/contracts/grokBrokerTaskClock.d.ts.map +1 -0
  7. package/dist/contracts/grokBrokerTaskClock.js +16 -0
  8. package/dist/contracts/organizationRuntimeContract.d.ts +76 -0
  9. package/dist/contracts/organizationRuntimeContract.d.ts.map +1 -1
  10. package/dist/contracts/organizationRuntimeContract.js +2 -0
  11. package/dist/contracts/runtimeContractManifest.d.ts +222 -15
  12. package/dist/contracts/runtimeContractManifest.d.ts.map +1 -1
  13. package/dist/contracts/runtimeContractManifest.js +35 -10
  14. package/dist/contracts/shellEnvironmentPolicy.d.ts +3 -0
  15. package/dist/contracts/shellEnvironmentPolicy.d.ts.map +1 -0
  16. package/dist/contracts/shellEnvironmentPolicy.js +5 -0
  17. package/dist/contracts/wakeMomentContract.d.ts +61 -0
  18. package/dist/contracts/wakeMomentContract.d.ts.map +1 -0
  19. package/dist/contracts/wakeMomentContract.js +27 -0
  20. package/dist/core/types.d.ts +2 -0
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/examples/jungianPlayAgent.d.ts +1 -0
  23. package/dist/examples/jungianPlayAgent.d.ts.map +1 -1
  24. package/dist/examples/jungianPlayAgent.js +6 -2
  25. package/dist/observability/causalEvents.d.ts +3 -3
  26. package/dist/observability/causalEvents.d.ts.map +1 -1
  27. package/dist/observability/causalEvents.js +4 -4
  28. package/dist/pi/cliEngineSpawn.d.ts +2 -2
  29. package/dist/pi/cliEngineSpawn.d.ts.map +1 -1
  30. package/dist/pi/cliEngineSpawn.js +7 -2
  31. package/dist/pi/cliEnvironment.d.ts.map +1 -1
  32. package/dist/pi/cliEnvironment.js +2 -0
  33. package/dist/pi/cliMcpRegistration.d.ts +1 -1
  34. package/dist/pi/cliMcpRegistration.d.ts.map +1 -1
  35. package/dist/pi/cliMcpRegistration.js +1 -1
  36. package/dist/pi/cliSession.d.ts +2 -0
  37. package/dist/pi/cliSession.d.ts.map +1 -1
  38. package/dist/pi/cliSession.js +3 -80
  39. package/dist/pi/cliSessionMcp.d.ts +9 -0
  40. package/dist/pi/cliSessionMcp.d.ts.map +1 -0
  41. package/dist/pi/cliSessionMcp.js +80 -0
  42. package/dist/pi/engineTaskClock.d.ts +9 -0
  43. package/dist/pi/engineTaskClock.d.ts.map +1 -0
  44. package/dist/pi/engineTaskClock.js +28 -0
  45. package/dist/pi/grokHomeMcpRegistration.d.ts.map +1 -1
  46. package/dist/pi/grokHomeMcpRegistration.js +2 -1
  47. package/dist/pi/grokSandbox.js +1 -1
  48. package/dist/pi/memoryClock.d.ts +25 -0
  49. package/dist/pi/memoryClock.d.ts.map +1 -0
  50. package/dist/pi/memoryClock.js +18 -0
  51. package/dist/pi/piAgentHandle.d.ts +3 -1
  52. package/dist/pi/piAgentHandle.d.ts.map +1 -1
  53. package/dist/pi/piAgentHandle.js +5 -2
  54. package/dist/pi/piHarness.d.ts +8 -2
  55. package/dist/pi/piHarness.d.ts.map +1 -1
  56. package/dist/pi/piHarness.js +28 -12
  57. package/dist/pi/taskClockProcess.d.ts +6 -0
  58. package/dist/pi/taskClockProcess.d.ts.map +1 -0
  59. package/dist/pi/taskClockProcess.js +8 -0
  60. package/dist/pi/turnCausal.d.ts +4 -2
  61. package/dist/pi/turnCausal.d.ts.map +1 -1
  62. package/dist/pi/turnCausal.js +4 -4
  63. package/dist/pi/wakeAcceptance.d.ts +1 -1
  64. package/dist/pi/wakeAcceptance.d.ts.map +1 -1
  65. package/dist/pi/wakeAcceptance.js +2 -2
  66. package/dist/runtime/agentSkillsDirectory.d.ts +25 -0
  67. package/dist/runtime/agentSkillsDirectory.d.ts.map +1 -0
  68. package/dist/runtime/agentSkillsDirectory.js +101 -0
  69. package/dist/runtime/agySubscriptionBootstrap.js +1 -1
  70. package/dist/runtime/agySubscriptionRealm.js +1 -1
  71. package/dist/runtime/attention.d.ts +5 -0
  72. package/dist/runtime/attention.d.ts.map +1 -1
  73. package/dist/runtime/attention.js +14 -1
  74. package/dist/runtime/attentionDispatcher.d.ts +14 -2
  75. package/dist/runtime/attentionDispatcher.d.ts.map +1 -1
  76. package/dist/runtime/attentionDispatcher.js +127 -33
  77. package/dist/runtime/cli.js +5 -0
  78. package/dist/runtime/contract-manifest.json +1 -1
  79. package/dist/runtime/contract-manifest.sha256 +1 -1
  80. package/dist/runtime/engineBrokerCapabilities.d.ts +10 -0
  81. package/dist/runtime/engineBrokerCapabilities.d.ts.map +1 -1
  82. package/dist/runtime/engineBrokerCapabilities.js +10 -0
  83. package/dist/runtime/engineBrokerControlClient.d.ts +12 -1
  84. package/dist/runtime/engineBrokerControlClient.d.ts.map +1 -1
  85. package/dist/runtime/engineBrokerControlClient.js +139 -43
  86. package/dist/runtime/engineBrokerCredentialJournal.d.ts +2 -0
  87. package/dist/runtime/engineBrokerCredentialJournal.d.ts.map +1 -1
  88. package/dist/runtime/engineBrokerCredentialJournal.js +9 -0
  89. package/dist/runtime/engineBrokerInferenceProtocol.d.ts +3 -2
  90. package/dist/runtime/engineBrokerInferenceProtocol.d.ts.map +1 -1
  91. package/dist/runtime/engineBrokerInferenceProtocol.js +4 -4
  92. package/dist/runtime/engineBrokerMcpCallLog.d.ts +11 -0
  93. package/dist/runtime/engineBrokerMcpCallLog.d.ts.map +1 -1
  94. package/dist/runtime/engineBrokerMcpCallLog.js +13 -1
  95. package/dist/runtime/engineBrokerMcpFacade.d.ts +5 -2
  96. package/dist/runtime/engineBrokerMcpFacade.d.ts.map +1 -1
  97. package/dist/runtime/engineBrokerMcpFacade.js +5 -2
  98. package/dist/runtime/engineBrokerNativeClient.js +2 -2
  99. package/dist/runtime/engineBrokerProtocol.d.ts +42 -10
  100. package/dist/runtime/engineBrokerProtocol.d.ts.map +1 -1
  101. package/dist/runtime/engineBrokerProtocol.js +97 -14
  102. package/dist/runtime/engineBrokerSealLedger.d.ts.map +1 -1
  103. package/dist/runtime/engineBrokerSealLedger.js +9 -0
  104. package/dist/runtime/engineBrokerService.d.ts +8 -2
  105. package/dist/runtime/engineBrokerService.d.ts.map +1 -1
  106. package/dist/runtime/engineBrokerService.js +95 -37
  107. package/dist/runtime/engineBrokerServiceCli.js +1 -1
  108. package/dist/runtime/engineBrokerServiceConfig.d.ts +15 -2
  109. package/dist/runtime/engineBrokerServiceConfig.d.ts.map +1 -1
  110. package/dist/runtime/engineBrokerServiceConfig.js +53 -11
  111. package/dist/runtime/engineBrokerTurnRegistry.d.ts +1 -1
  112. package/dist/runtime/engineBrokerTurnRegistry.d.ts.map +1 -1
  113. package/dist/runtime/engineBrokerTurnRegistry.js +7 -6
  114. package/dist/runtime/engineDispatcher.d.ts +2 -0
  115. package/dist/runtime/engineDispatcher.d.ts.map +1 -1
  116. package/dist/runtime/engineDispatcher.js +14 -2
  117. package/dist/runtime/engineReadiness.js +1 -1
  118. package/dist/runtime/grokBrokerCredentialAuthority.d.ts +1 -0
  119. package/dist/runtime/grokBrokerCredentialAuthority.d.ts.map +1 -1
  120. package/dist/runtime/grokBrokerCredentialAuthority.js +48 -22
  121. package/dist/runtime/grokBrokerCredentialReader.d.ts +1 -0
  122. package/dist/runtime/grokBrokerCredentialReader.d.ts.map +1 -1
  123. package/dist/runtime/grokBrokerCredentialReader.js +2 -1
  124. package/dist/runtime/grokBrokerProjection.d.ts +26 -2
  125. package/dist/runtime/grokBrokerProjection.d.ts.map +1 -1
  126. package/dist/runtime/grokBrokerProjection.js +18 -7
  127. package/dist/runtime/grokBrokerProviderClock.d.ts +26 -0
  128. package/dist/runtime/grokBrokerProviderClock.d.ts.map +1 -0
  129. package/dist/runtime/grokBrokerProviderClock.js +133 -0
  130. package/dist/runtime/grokBrokerProxy.d.ts +17 -3
  131. package/dist/runtime/grokBrokerProxy.d.ts.map +1 -1
  132. package/dist/runtime/grokBrokerProxy.js +73 -9
  133. package/dist/runtime/grokBrokerProxyJson.d.ts +10 -0
  134. package/dist/runtime/grokBrokerProxyJson.d.ts.map +1 -0
  135. package/dist/runtime/grokBrokerProxyJson.js +31 -0
  136. package/dist/runtime/grokBrokerProxyRequest.d.ts.map +1 -1
  137. package/dist/runtime/grokBrokerProxyRequest.js +17 -6
  138. package/dist/runtime/grokBrokerRealmLease.js +1 -1
  139. package/dist/runtime/grokBrokerRefresh.js +1 -1
  140. package/dist/runtime/grokBrokerRunBudget.d.ts +20 -0
  141. package/dist/runtime/grokBrokerRunBudget.d.ts.map +1 -0
  142. package/dist/runtime/grokBrokerRunBudget.js +85 -0
  143. package/dist/runtime/grokBrokerSlot.d.ts +6 -0
  144. package/dist/runtime/grokBrokerSlot.d.ts.map +1 -0
  145. package/dist/runtime/grokBrokerSlot.js +21 -0
  146. package/dist/runtime/grokBrokerTaskClock.d.ts +32 -0
  147. package/dist/runtime/grokBrokerTaskClock.d.ts.map +1 -0
  148. package/dist/runtime/grokBrokerTaskClock.js +120 -0
  149. package/dist/runtime/grokBrokerTurnMeter.d.ts +55 -3
  150. package/dist/runtime/grokBrokerTurnMeter.d.ts.map +1 -1
  151. package/dist/runtime/grokBrokerTurnMeter.js +90 -4
  152. package/dist/runtime/grokBrokerWorkerConfig.d.ts +5 -2
  153. package/dist/runtime/grokBrokerWorkerConfig.d.ts.map +1 -1
  154. package/dist/runtime/grokBrokerWorkerConfig.js +22 -3
  155. package/dist/runtime/grokEngineBroker.d.ts +43 -3
  156. package/dist/runtime/grokEngineBroker.d.ts.map +1 -1
  157. package/dist/runtime/grokEngineBroker.js +159 -71
  158. package/dist/runtime/grokEngineBrokerTurn.d.ts +32 -8
  159. package/dist/runtime/grokEngineBrokerTurn.d.ts.map +1 -1
  160. package/dist/runtime/grokEngineBrokerTurn.js +204 -21
  161. package/dist/runtime/grokEngineBrokerTurnEnd.d.ts +88 -0
  162. package/dist/runtime/grokEngineBrokerTurnEnd.d.ts.map +1 -0
  163. package/dist/runtime/grokEngineBrokerTurnEnd.js +148 -0
  164. package/dist/runtime/grokInferenceProxy.d.ts.map +1 -1
  165. package/dist/runtime/grokInferenceProxy.js +3 -0
  166. package/dist/runtime/grokSubscriptionRealm.js +1 -1
  167. package/dist/runtime/grokWorkerAttestation.d.ts +12 -5
  168. package/dist/runtime/grokWorkerAttestation.d.ts.map +1 -1
  169. package/dist/runtime/grokWorkerAttestation.js +5 -3
  170. package/dist/runtime/grokWorkerHomeAttestation.d.ts +51 -5
  171. package/dist/runtime/grokWorkerHomeAttestation.d.ts.map +1 -1
  172. package/dist/runtime/grokWorkerHomeAttestation.js +103 -8
  173. package/dist/runtime/grokWorkerSkills.d.ts +86 -0
  174. package/dist/runtime/grokWorkerSkills.d.ts.map +1 -0
  175. package/dist/runtime/grokWorkerSkills.js +135 -0
  176. package/dist/runtime/index.d.ts +10 -2
  177. package/dist/runtime/index.d.ts.map +1 -1
  178. package/dist/runtime/index.js +8 -1
  179. package/dist/runtime/native/daimon-engine-broker +0 -0
  180. package/dist/runtime/organizationRuntime.d.ts +3 -0
  181. package/dist/runtime/organizationRuntime.d.ts.map +1 -1
  182. package/dist/runtime/organizationRuntimeAdmission.d.ts +6 -0
  183. package/dist/runtime/organizationRuntimeAdmission.d.ts.map +1 -0
  184. package/dist/runtime/organizationRuntimeAdmission.js +22 -0
  185. package/dist/runtime/organizationRuntimeControl.d.ts +9 -0
  186. package/dist/runtime/organizationRuntimeControl.d.ts.map +1 -1
  187. package/dist/runtime/organizationRuntimeControl.js +72 -24
  188. package/dist/runtime/organizationRuntimeHost.d.ts.map +1 -1
  189. package/dist/runtime/organizationRuntimeHost.js +13 -4
  190. package/dist/runtime/organizationRuntimeParsing.d.ts.map +1 -1
  191. package/dist/runtime/organizationRuntimeParsing.js +4 -1
  192. package/dist/runtime/organizationRuntimeReadiness.d.ts.map +1 -1
  193. package/dist/runtime/organizationRuntimeReadiness.js +8 -0
  194. package/dist/runtime/organizationRuntimeRecording.d.ts +7 -0
  195. package/dist/runtime/organizationRuntimeRecording.d.ts.map +1 -0
  196. package/dist/runtime/organizationRuntimeRecording.js +58 -0
  197. package/dist/runtime/physicalReadiness.d.ts +12 -0
  198. package/dist/runtime/physicalReadiness.d.ts.map +1 -1
  199. package/dist/runtime/physicalReadiness.js +43 -9
  200. package/dist/runtime/productionAgentTools.d.ts.map +1 -1
  201. package/dist/runtime/productionAgentTools.js +13 -5
  202. package/dist/runtime/relocateRuntimeConfig.d.ts +14 -0
  203. package/dist/runtime/relocateRuntimeConfig.d.ts.map +1 -0
  204. package/dist/runtime/relocateRuntimeConfig.js +38 -0
  205. package/dist/runtime/schedule.d.ts.map +1 -1
  206. package/dist/runtime/schedule.js +12 -7
  207. package/dist/runtime/storeCoordination.d.ts +10 -0
  208. package/dist/runtime/storeCoordination.d.ts.map +1 -1
  209. package/dist/runtime/storeCoordination.js +23 -0
  210. package/dist/runtime/taskClock.d.ts +42 -0
  211. package/dist/runtime/taskClock.d.ts.map +1 -0
  212. package/dist/runtime/taskClock.js +102 -0
  213. package/dist/runtime/taskClockProcess.d.ts +17 -0
  214. package/dist/runtime/taskClockProcess.d.ts.map +1 -0
  215. package/dist/runtime/taskClockProcess.js +58 -0
  216. package/dist/runtime/turnRequestLedger.d.ts +0 -5
  217. package/dist/runtime/turnRequestLedger.d.ts.map +1 -1
  218. package/dist/runtime/turnRequestLedger.js +23 -21
  219. package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts +34 -0
  220. package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts.map +1 -0
  221. package/dist/runtime/wakeAcceptanceClaimDiagnostics.js +79 -0
  222. package/dist/runtime/wakeAcceptanceIndex.d.ts +55 -0
  223. package/dist/runtime/wakeAcceptanceIndex.d.ts.map +1 -0
  224. package/dist/runtime/wakeAcceptanceIndex.js +158 -0
  225. package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts +9 -1
  226. package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts.map +1 -1
  227. package/dist/runtime/wakeAcceptanceProcessIdentity.js +5 -1
  228. package/dist/runtime/wakeAcceptanceRecord.d.ts +2 -0
  229. package/dist/runtime/wakeAcceptanceRecord.d.ts.map +1 -1
  230. package/dist/runtime/wakeAcceptanceRecord.js +4 -2
  231. package/dist/runtime/wakeAcceptanceRetention.d.ts +25 -1
  232. package/dist/runtime/wakeAcceptanceRetention.d.ts.map +1 -1
  233. package/dist/runtime/wakeAcceptanceRetention.js +28 -2
  234. package/dist/runtime/wakeAcceptanceStore.d.ts +8 -8
  235. package/dist/runtime/wakeAcceptanceStore.d.ts.map +1 -1
  236. package/dist/runtime/wakeAcceptanceStore.js +66 -63
  237. package/dist/runtime/wakeFuse.d.ts +58 -3
  238. package/dist/runtime/wakeFuse.d.ts.map +1 -1
  239. package/dist/runtime/wakeFuse.js +106 -7
  240. package/dist/runtime/wakeMomentFs.d.ts +27 -0
  241. package/dist/runtime/wakeMomentFs.d.ts.map +1 -0
  242. package/dist/runtime/wakeMomentFs.js +223 -0
  243. package/dist/runtime/wakeMomentHistory.d.ts +26 -0
  244. package/dist/runtime/wakeMomentHistory.d.ts.map +1 -0
  245. package/dist/runtime/wakeMomentHistory.js +109 -0
  246. package/dist/runtime/wakeMomentIo.d.ts +22 -0
  247. package/dist/runtime/wakeMomentIo.d.ts.map +1 -0
  248. package/dist/runtime/wakeMomentIo.js +114 -0
  249. package/dist/runtime/wakeMomentRecorder.d.ts +33 -0
  250. package/dist/runtime/wakeMomentRecorder.d.ts.map +1 -0
  251. package/dist/runtime/wakeMomentRecorder.js +81 -0
  252. package/dist/runtime/wakeMomentRetention.d.ts +7 -0
  253. package/dist/runtime/wakeMomentRetention.d.ts.map +1 -0
  254. package/dist/runtime/wakeMomentRetention.js +143 -0
  255. package/dist/runtime/wakeMomentRows.d.ts +8 -0
  256. package/dist/runtime/wakeMomentRows.d.ts.map +1 -0
  257. package/dist/runtime/wakeMomentRows.js +66 -0
  258. package/dist/runtime/wakeMomentSnapshot.d.ts +56 -0
  259. package/dist/runtime/wakeMomentSnapshot.d.ts.map +1 -0
  260. package/dist/runtime/wakeMomentSnapshot.js +248 -0
  261. package/package.json +4 -4
@@ -0,0 +1,101 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, open, readdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { AGENT_SKILLS_BOUNDS, parseAgentSkills } from "./grokWorkerSkills.js";
5
+ const invalid = (detail) => new TypeError(`invalid agent skills directory: ${detail}`);
6
+ const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
7
+ /**
8
+ * Reads a skills tree — `<root>/<name>/SKILL.md` plus any supporting files —
9
+ * into the engine-neutral {@link AgentSkill} input, or `undefined` when `root`
10
+ * does not exist.
11
+ *
12
+ * It is the same reader for both ends of the Grok skills path: provisioning
13
+ * reads the compiled workspace's `.agents/skills` (the tree Codex reads in
14
+ * place), and the broker re-reads the rendered `$GROK_HOME/skills` before every
15
+ * turn under a root-owned, read-only policy. Symlinks, special files, hard
16
+ * links (read-only policy), non-UTF-8 content and anything past the
17
+ * {@link AGENT_SKILLS_BOUNDS} are refused rather than skipped, because a
18
+ * skipped entry would make the attested digest describe less than Grok loads.
19
+ */
20
+ export async function readAgentSkillsDirectory(root, policy = {}) {
21
+ let top;
22
+ try {
23
+ top = await lstat(root);
24
+ }
25
+ catch (error) {
26
+ if (error.code === "ENOENT")
27
+ return undefined;
28
+ throw invalid("unreadable root");
29
+ }
30
+ assertEntry(top, "directory", policy, ".");
31
+ const names = await boundedEntries(root, AGENT_SKILLS_BOUNDS.skills);
32
+ const skills = [];
33
+ let budget = AGENT_SKILLS_BOUNDS.totalBytes;
34
+ for (const name of names) {
35
+ const directory = path.join(root, name);
36
+ assertEntry(await lstat(directory), "directory", policy, name);
37
+ const files = [];
38
+ const walk = async (relative, depth) => {
39
+ for (const child of await boundedEntries(path.join(directory, relative), AGENT_SKILLS_BOUNDS.filesPerSkill)) {
40
+ const childRelative = relative === "" ? child : `${relative}/${child}`;
41
+ const absolute = path.join(directory, childRelative);
42
+ const where = `${name}/${childRelative}`;
43
+ const entry = await lstat(absolute);
44
+ if (entry.isDirectory()) {
45
+ if (depth + 1 >= AGENT_SKILLS_BOUNDS.depth)
46
+ throw invalid(`too deep at ${JSON.stringify(where)}`);
47
+ assertEntry(entry, "directory", policy, where);
48
+ await walk(childRelative, depth + 1);
49
+ continue;
50
+ }
51
+ assertEntry(entry, "file", policy, where);
52
+ if (files.length >= AGENT_SKILLS_BOUNDS.filesPerSkill)
53
+ throw invalid(`too many files in ${JSON.stringify(name)}`);
54
+ const content = await readPinnedFile(absolute, where, entry, Math.min(AGENT_SKILLS_BOUNDS.fileBytes, budget));
55
+ budget -= Buffer.byteLength(content, "utf8");
56
+ files.push({ path: childRelative, content });
57
+ }
58
+ };
59
+ await walk("", 0);
60
+ skills.push({ name, files });
61
+ }
62
+ return parseAgentSkills(skills);
63
+ }
64
+ /** `where` is the entry's path relative to the skills root, named in every refusal. */
65
+ function assertEntry(entry, kind, policy, where) {
66
+ if (entry.isSymbolicLink() || (kind === "directory" ? !entry.isDirectory() : !entry.isFile()))
67
+ throw invalid(`expected a plain ${kind} at ${JSON.stringify(where)}`);
68
+ if (policy.ownerUid !== undefined && entry.uid !== policy.ownerUid)
69
+ throw invalid(`unexpected owner of ${JSON.stringify(where)}`);
70
+ if (policy.readOnly === true && ((Number(entry.mode) & 0o7222) !== 0 || (kind === "file" && entry.nlink !== 1)))
71
+ throw invalid(`writable or linked entry ${JSON.stringify(where)}`);
72
+ }
73
+ async function boundedEntries(directory, limit) {
74
+ const entries = await readdir(directory);
75
+ if (entries.length > limit)
76
+ throw invalid(`too many entries in ${JSON.stringify(path.basename(directory))}`);
77
+ return entries.sort();
78
+ }
79
+ /** Opens without following links and requires the inode `lstat` saw, so a swap between the two cannot change the bytes read. */
80
+ async function readPinnedFile(file, where, seen, limit) {
81
+ if (seen.size > limit)
82
+ throw invalid(`file too large ${JSON.stringify(where)}`);
83
+ const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
84
+ try {
85
+ const opened = await handle.stat();
86
+ if (!opened.isFile() || opened.dev !== seen.dev || opened.ino !== seen.ino || opened.uid !== seen.uid || opened.mode !== seen.mode || opened.nlink !== seen.nlink || opened.size > limit)
87
+ throw invalid(`file changed while read ${JSON.stringify(where)}`);
88
+ const bytes = await handle.readFile();
89
+ if (bytes.length > limit)
90
+ throw invalid(`file too large ${JSON.stringify(where)}`);
91
+ try {
92
+ return decoder.decode(bytes);
93
+ }
94
+ catch {
95
+ throw invalid(`file is not UTF-8 ${JSON.stringify(where)}`);
96
+ }
97
+ }
98
+ finally {
99
+ await handle.close().catch(() => undefined);
100
+ }
101
+ }
@@ -1,4 +1,4 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawnRealTime as spawn } from "./taskClockProcess.js";
2
2
  import { cliChildEnvironment } from "../pi/cliEnvironment.js";
3
3
  import { startAgySubscriptionRealm } from "./agySubscriptionRealm.js";
4
4
  import { prepareEngineExecutable, verifyAgySubscriptionEnrollment } from "./engineReadiness.js";
@@ -1,5 +1,5 @@
1
1
  import { constants } from "node:fs";
2
- import { spawn } from "node:child_process";
2
+ import { spawnRealTime as spawn } from "./taskClockProcess.js";
3
3
  import { chmod, lstat, mkdir, mkdtemp, open, realpath, rm } from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
@@ -1,4 +1,5 @@
1
1
  import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ import type { StoredWakeAcceptanceRecord } from "./wakeAcceptanceRecord.js";
2
3
  import type { AttentionConfig } from "../contracts/attentionContract.js";
3
4
  export type { AttentionConfig } from "../contracts/attentionContract.js";
4
5
  export type AttentionMessage = Readonly<{
@@ -10,12 +11,16 @@ export type AttentionMessage = Readonly<{
10
11
  }>;
11
12
  export type AttentionTurn = Readonly<{
12
13
  executionId: string;
14
+ /** Projected once by the dispatcher; already in the agent's clock domain. */
13
15
  messages: readonly AttentionMessage[];
14
16
  budget(): Promise<unknown>;
15
17
  disposition(deliveryId: string, disposition: "complete" | "defer"): Promise<void>;
16
18
  }>;
17
19
  /** Owned by one control host; never shared across hosts or agent identities. */
18
20
  export type AttentionRegistry = Map<string, AttentionTurn>;
21
+ /** Project real delivery instants, preserving queue age and relative offsets.
22
+ * Only the private durable provenance exempts a native task-calendar occurrence. */
23
+ export declare function taskClockAttentionMessages(records: readonly Pick<StoredWakeAcceptanceRecord, "acceptance_id" | "delivery_id" | "event" | "native_schedule">[]): readonly AttentionMessage[];
19
24
  export declare function attentionTools(agentId: string, registry: AttentionRegistry): ToolDefinition[];
20
25
  export declare function parseAttention(value: unknown): AttentionConfig;
21
26
  //# sourceMappingURL=attention.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attention.d.ts","sourceRoot":"","sources":["../../src/runtime/attention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,YAAY,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACzI,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnF,CAAC,CAAC;AACH,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,cAAc,EAAE,CAqB7F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAS9D"}
1
+ {"version":3,"file":"attention.d.ts","sourceRoot":"","sources":["../../src/runtime/attention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAE5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,YAAY,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACzI,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnF,CAAC,CAAC;AACH,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D;oFACoF;AACpF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,SAAS,IAAI,CAAC,0BAA0B,EAAE,eAAe,GAAG,aAAa,GAAG,OAAO,GAAG,iBAAiB,CAAC,EAAE,GAAG,SAAS,gBAAgB,EAAE,CAK3L;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,cAAc,EAAE,CAyB7F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAS9D"}
@@ -1,3 +1,12 @@
1
+ import { readTaskClock, taskClockTimestamp } from "./taskClock.js";
2
+ /** Project real delivery instants, preserving queue age and relative offsets.
3
+ * Only the private durable provenance exempts a native task-calendar occurrence. */
4
+ export function taskClockAttentionMessages(records) {
5
+ const clock = readTaskClock();
6
+ return records.map((record) => ({ acceptance_id: record.acceptance_id, delivery_id: record.delivery_id, ...record.event,
7
+ occurred_at: record.native_schedule === true ? record.event.occurred_at : taskClockTimestamp(record.event.occurred_at, clock)
8
+ }));
9
+ }
1
10
  export function attentionTools(agentId, registry) {
2
11
  const current = () => { const turn = registry.get(agentId); if (!turn)
3
12
  throw new Error("No active inbox turn"); return turn; };
@@ -6,7 +15,11 @@ export function attentionTools(agentId, registry) {
6
15
  parameters: { type: "object", additionalProperties: false, properties: {} },
7
16
  async execute() {
8
17
  const turn = current();
9
- const details = { version: "noopolis.daimon.inbox.v1", execution_id: turn.executionId, messages: turn.messages, budget: await turn.budget() };
18
+ const budget = await turn.budget();
19
+ const clock = readTaskClock();
20
+ const visibleBudget = clock !== undefined && budget !== null && typeof budget === "object" && "epoch" in budget && typeof budget.epoch === "string"
21
+ ? { ...budget, epoch: budget.epoch.replace(/^(organization-[a-f0-9]{64}-)\d{4}-\d{2}-\d{2}$/u, `$1${new Date(clock.now()).toISOString().slice(0, 10)}`) } : budget;
22
+ const details = { version: "noopolis.daimon.inbox.v1", execution_id: turn.executionId, messages: turn.messages, budget: visibleBudget };
10
23
  return { content: [{ type: "text", text: JSON.stringify(details) }], details };
11
24
  }
12
25
  }, {
@@ -1,8 +1,9 @@
1
- import type { AttentionRegistry } from "./attention.js";
1
+ import { type AttentionRegistry } from "./attention.js";
2
2
  import type { OrganizationRuntimeAgentConfig, OrganizationRuntimeHost } from "./organizationRuntime.js";
3
3
  import { WakeAcceptanceStore } from "./wakeAcceptanceStore.js";
4
- import type { StoredWakeAcceptanceRecord } from "./wakeAcceptanceRecord.js";
4
+ import { type StoredWakeAcceptanceRecord } from "./wakeAcceptanceRecord.js";
5
5
  import { WakeFuse } from "./wakeFuse.js";
6
+ import { type WakeMomentOptions } from "./wakeMomentRecorder.js";
6
7
  type Options = Readonly<{
7
8
  store: WakeAcceptanceStore;
8
9
  host: OrganizationRuntimeHost;
@@ -11,6 +12,7 @@ type Options = Readonly<{
11
12
  registry: AttentionRegistry;
12
13
  token: string | undefined;
13
14
  onIdle(agentId: string): void;
15
+ recordingOptionsForTest?: WakeMomentOptions;
14
16
  }>;
15
17
  /** Durable deliveries wait here; only a selected execution reserves a budget slot. */
16
18
  export declare class AttentionDispatcher {
@@ -23,6 +25,10 @@ export declare class AttentionDispatcher {
23
25
  private readonly active;
24
26
  private readonly errors;
25
27
  private stopping;
28
+ /** Reversible drain: no new execution starts; durable deliveries stay queued. */
29
+ private paused;
30
+ /** Agents whose inbox loop gave up its turn to a drain; resume owes each one a pass. */
31
+ private readonly pausedOut;
26
32
  private fatal;
27
33
  constructor(options: Options);
28
34
  notify(agentId: string, fresh?: boolean): void;
@@ -37,6 +43,12 @@ export declare class AttentionDispatcher {
37
43
  failure(agentId: string): string | undefined;
38
44
  stop(): Promise<void>;
39
45
  halt(): void;
46
+ /** Stops admitting executions; a running turn finishes and its records settle as usual. */
47
+ pause(): void;
48
+ /** Admits again and revisits every agent whose inbox moved while paused. */
49
+ resume(queued?: Iterable<string>): void;
50
+ /** True once no inbox loop is alive: nothing runs, and nothing can start until resume. */
51
+ quiescent(): boolean;
40
52
  private drain;
41
53
  private execute;
42
54
  private failClosed;
@@ -1 +1 @@
1
- {"version":3,"file":"attentionDispatcher.d.ts","sourceRoot":"","sources":["../../src/runtime/attentionDispatcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,8BAA8B,EAAE,uBAAuB,EAAiC,MAAM,0BAA0B,CAAC;AAEvI,OAAO,EAAE,mBAAmB,EAAwD,MAAM,0BAA0B,CAAC;AACrH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAKzC,KAAK,OAAO,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,uBAAuB,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,SAAS,8BAA8B,EAAE,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC;AAEjP,sFAAsF;AACtF,qBAAa,mBAAmB;IAUlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IATpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuE;IAC9F,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,KAAK,CAAmC;gBACnB,OAAO,EAAE,OAAO;IAE7C,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,IAAI;IAW5C,WAAW,IAAI,oBAAoB,GAAG,SAAS;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAC9B,gBAAgB;;sBAnB0C,MAAM;sBAAgB,MAAM,EAAE;;;IAoBxF,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IACtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,IAAI,IAAI,IAAI;YAEE,KAAK;YA2BL,OAAO;IAqErB,OAAO,CAAC,UAAU;YASJ,SAAS;CAOxB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,0BAA0B,EAAE,EAAE,KAAK,EAAE,8BAA8B,EAAE,eAAe,EAAE,OAAO,GAAG,0BAA0B,EAAE,CAmBzK;AAcD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,MAAM,EAAE,8BAA8B,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,SAAQ,GAAG,MAAM,CAsB5I"}
1
+ {"version":3,"file":"attentionDispatcher.d.ts","sourceRoot":"","sources":["../../src/runtime/attentionDispatcher.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,KAAK,EAAE,8BAA8B,EAAE,uBAAuB,EAAiC,MAAM,0BAA0B,CAAC;AAEvI,OAAO,EAAE,mBAAmB,EAAwD,MAAM,0BAA0B,CAAC;AACrH,OAAO,EAA0B,KAAK,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAEpG,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGnF,KAAK,OAAO,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,uBAAuB,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,SAAS,8BAA8B,EAAE,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,uBAAuB,CAAC,EAAE,iBAAiB,CAAA;CAAE,CAAC,CAAC;AAE9R,sFAAsF;AACtF,qBAAa,mBAAmB;IAclB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAbpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuE;IAC9F,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAS;IACzB,iFAAiF;IACjF,OAAO,CAAC,MAAM,CAAS;IACvB,wFAAwF;IACxF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,OAAO,CAAC,KAAK,CAAmC;gBACnB,OAAO,EAAE,OAAO;IAE7C,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,IAAI;IAkB5C,WAAW,IAAI,oBAAoB,GAAG,SAAS;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAC9B,gBAAgB;;sBA9B0C,MAAM;sBAAgB,MAAM,EAAE;;;IA+BxF,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IACtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,IAAI,IAAI,IAAI;IACZ,2FAA2F;IAC3F,KAAK,IAAI,IAAI;IACb,4EAA4E;IAC5E,MAAM,CAAC,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAM,GAAG,IAAI;IAO3C,0FAA0F;IAC1F,SAAS,IAAI,OAAO;YAEN,KAAK;YA0CL,OAAO;IAmFrB,OAAO,CAAC,UAAU;YASJ,SAAS;CAOxB;AAkBD,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,0BAA0B,EAAE,EAAE,KAAK,EAAE,8BAA8B,EAAE,eAAe,EAAE,OAAO,GAAG,0BAA0B,EAAE,CAmBzK;AAcD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,MAAM,EAAE,8BAA8B,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,SAAQ,GAAG,MAAM,CAsB5I"}
@@ -1,8 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { taskClockAttentionMessages } from "./attention.js";
2
3
  import { engineFailureDetail } from "./organizationRuntimeHost.js";
3
4
  import { WakeExecutionClaimLostError } from "./wakeAcceptanceStore.js";
5
+ import { sanitizeExecutionError } from "./wakeAcceptanceRecord.js";
6
+ import { STALE_QUEUED_DELIVERY_MS } from "./wakeAcceptanceRetention.js";
4
7
  import { ORGANIZATION_RUNTIME_MAX_STRING_CODEPOINTS, ORGANIZATION_RUNTIME_MAX_WAKE_TEXT_BYTES } from "../contracts/organizationRuntimeContract.js";
5
8
  import { grokDaimonToolName } from "../contracts/grokWorkerContract.js";
9
+ import { verifyHostRecordingStore, wakeFromDurableInbox } from "./organizationRuntimeAdmission.js";
10
+ import { recordWakeMoment } from "./wakeMomentRecorder.js";
6
11
  /** Durable deliveries wait here; only a selected execution reserves a budget slot. */
7
12
  export class AttentionDispatcher {
8
13
  options;
@@ -14,6 +19,10 @@ export class AttentionDispatcher {
14
19
  active = new Map();
15
20
  errors = new Map();
16
21
  stopping = false;
22
+ /** Reversible drain: no new execution starts; durable deliveries stay queued. */
23
+ paused = false;
24
+ /** Agents whose inbox loop gave up its turn to a drain; resume owes each one a pass. */
25
+ pausedOut = new Set();
17
26
  fatal;
18
27
  constructor(options) {
19
28
  this.options = options;
@@ -21,11 +30,22 @@ export class AttentionDispatcher {
21
30
  notify(agentId, fresh = false) {
22
31
  if (fresh)
23
32
  this.generations.set(agentId, (this.generations.get(agentId) ?? 0) + 1);
24
- if (this.stopping || this.work.has(agentId))
33
+ if (this.stopping)
34
+ return;
35
+ // Every pass a drain withholds is owed back on resume.
36
+ if (this.paused) {
37
+ this.pausedOut.add(agentId);
38
+ return;
39
+ }
40
+ if (this.work.has(agentId))
25
41
  return;
26
- const task = this.drain(agentId).catch((error) => { this.observed.set(agentId, this.generations.get(agentId) ?? 0); this.errors.set(agentId, engineFailureDetail(error) ?? "inbox_storage_unavailable"); }).finally(() => {
42
+ // An owed pass is not new input: it starts from the generation already seen, so
43
+ // deferred deliveries stay deferred unless something really arrived meanwhile.
44
+ const baseline = this.pausedOut.delete(agentId) ? this.observed.get(agentId) ?? 0 : 0;
45
+ const task = this.drain(agentId, baseline).catch((error) => { this.observed.set(agentId, this.generations.get(agentId) ?? 0); this.errors.set(agentId, engineFailureDetail(error) ?? "inbox_storage_unavailable"); }).finally(() => {
27
46
  this.work.delete(agentId);
28
- if (!this.stopping && (this.generations.get(agentId) ?? 0) > (this.observed.get(agentId) ?? 0))
47
+ // A resume that landed while this loop was still unwinding its paused exit found it busy; run the owed pass now.
48
+ if (!this.stopping && ((this.generations.get(agentId) ?? 0) > (this.observed.get(agentId) ?? 0) || this.pausedOut.has(agentId)))
29
49
  this.notify(agentId);
30
50
  this.options.onIdle(agentId);
31
51
  });
@@ -44,12 +64,36 @@ export class AttentionDispatcher {
44
64
  }
45
65
  halt() { this.stopping = true; for (const finish of this.waiters)
46
66
  finish(); }
47
- async drain(agentId) {
67
+ /** Stops admitting executions; a running turn finishes and its records settle as usual. */
68
+ pause() { this.paused = true; for (const finish of this.waiters)
69
+ finish(); }
70
+ /** Admits again and revisits every agent whose inbox moved while paused. */
71
+ resume(queued = []) {
72
+ this.paused = false;
73
+ // Agents with queued work get an owed pass too: a delivery parked before the
74
+ // drain (a budget pause, say) has no generation of its own to wake it.
75
+ for (const agentId of queued)
76
+ this.pausedOut.add(agentId);
77
+ for (const agent of this.options.agents)
78
+ if ((this.generations.get(agent.id) ?? 0) > (this.observed.get(agent.id) ?? 0) || this.pausedOut.has(agent.id))
79
+ this.notify(agent.id);
80
+ }
81
+ /** True once no inbox loop is alive: nothing runs, and nothing can start until resume. */
82
+ quiescent() { return this.work.size === 0; }
83
+ async drain(agentId, baseline = 0) {
48
84
  const agent = this.options.agents.find((value) => value.id === agentId);
49
- let observedGeneration = 0;
85
+ let observedGeneration = baseline;
50
86
  while (!this.stopping) {
87
+ if (this.paused) {
88
+ this.pausedOut.add(agentId);
89
+ return;
90
+ }
51
91
  const generation = this.generations.get(agentId) ?? 0;
52
- const records = (await this.options.store.recoverable(new Set(this.options.agents.map((value) => value.id)))).filter((record) => record.agent_id === agentId);
92
+ const stale = [];
93
+ const records = (await this.options.store.recoverable(new Set(this.options.agents.map((value) => value.id)), (record) => { if (record.agent_id === agentId)
94
+ stale.push(record); })).filter((record) => record.agent_id === agentId);
95
+ for (const record of stale)
96
+ await expireStaleDelivery(this.options.store, record);
53
97
  const selected = selectBatch(records, agent, generation > observedGeneration);
54
98
  observedGeneration = generation;
55
99
  this.observed.set(agentId, generation);
@@ -59,6 +103,10 @@ export class AttentionDispatcher {
59
103
  const budget = await this.options.fuse.snapshot(agentId, agent.attention);
60
104
  if (budget.state !== "available")
61
105
  return;
106
+ if (this.paused) {
107
+ this.pausedOut.add(agentId);
108
+ return;
109
+ }
62
110
  const executionId = selected[0].execution_id ?? randomUUID();
63
111
  const acquired = await this.options.store.acquireClaim(selected[0].acceptance_id, this.owner, selected.map((record) => record.acceptance_id), executionId);
64
112
  if (acquired.state === "held") {
@@ -70,8 +118,22 @@ export class AttentionDispatcher {
70
118
  const claimed = selected.map((record) => ({ record, claim: acquired.claim, done: false }));
71
119
  for (const item of claimed)
72
120
  item.record = await this.options.store.transitionClaimed(item.record.acceptance_id, item.claim, "running", undefined, undefined, agent.attention === undefined ? undefined : { execution_id: executionId, deferred: false });
73
- const verdict = await this.options.fuse.admit(agentId, randomUUID(), agent.attention);
74
- if (verdict.state !== "admitted" || this.stopping) {
121
+ // A member ended between selection and claim (another host's expiry or stop
122
+ // reached it first): never wake cognition on a batch that includes it.
123
+ if (claimed.some((item) => item.record.state !== "running")) {
124
+ for (const item of claimed.filter((value) => value.record.state === "running"))
125
+ await this.options.store.transitionClaimed(item.record.acceptance_id, item.claim, "accepted");
126
+ await this.options.store.releaseClaim(claimed[0].claim);
127
+ continue;
128
+ }
129
+ // A drain that arrived after the claim releases it before any budget is spent.
130
+ // Admission is the drain's linearization point: a turn the fuse admitted before
131
+ // the loop saw the drain is a running turn, and keeps the host "draining".
132
+ const pausedBeforeAdmit = this.paused;
133
+ const verdict = pausedBeforeAdmit ? undefined : await this.options.fuse.admit(agentId, randomUUID(), agent.attention);
134
+ if (verdict?.state !== "admitted" || this.stopping) {
135
+ if (pausedBeforeAdmit)
136
+ this.pausedOut.add(agentId);
75
137
  for (const item of claimed)
76
138
  await this.options.store.transitionClaimed(item.record.acceptance_id, item.claim, "accepted");
77
139
  await this.options.store.releaseClaim(claimed[0].claim);
@@ -101,31 +163,34 @@ export class AttentionDispatcher {
101
163
  }).catch((error) => this.failClosed(agent.id, error));
102
164
  }, store.claimHeartbeatIntervalMs());
103
165
  this.active.set(agent.id, { execution_id: agent.attention === undefined ? claimed[0].record.delivery_id : executionId, delivery_ids: claimed.map((item) => item.record.delivery_id) });
104
- const messages = claimed.map(({ record }) => ({ acceptance_id: record.acceptance_id, delivery_id: record.delivery_id, ...record.event }));
105
- if (agent.attention !== undefined)
106
- registry.set(agent.id, {
107
- executionId, messages, budget: () => fuse.snapshot(agent.id, agent.attention),
108
- disposition: (deliveryId, disposition) => serialize(async () => {
109
- const item = claimed.find((value) => value.record.delivery_id === deliveryId);
110
- if (!item)
111
- throw new Error("Delivery is outside this agent's selected inbox turn");
112
- if (item.done && item.record.state === "completed") {
113
- if (disposition !== "complete")
114
- throw new Error("A completed delivery cannot be deferred");
115
- return;
116
- }
117
- if (item.done && disposition === "defer")
118
- return;
119
- item.record = await store.transitionClaimed(item.record.acceptance_id, item.claim, disposition === "complete" ? "completed" : "accepted", undefined, disposition === "complete" ? "" : undefined, disposition === "defer" ? { deferred: true, clear_execution: true, execution_error: null } : { execution_id: executionId, deferred: false, execution_error: null });
120
- item.done = true;
121
- })
122
- });
123
166
  let result;
124
167
  try {
168
+ const messages = taskClockAttentionMessages(claimed.map(({ record }) => record));
169
+ if (agent.attention !== undefined)
170
+ registry.set(agent.id, {
171
+ executionId, messages, budget: () => fuse.snapshot(agent.id, agent.attention),
172
+ disposition: (deliveryId, disposition) => serialize(async () => {
173
+ const item = claimed.find((value) => value.record.delivery_id === deliveryId);
174
+ if (!item)
175
+ throw new Error("Delivery is outside this agent's selected inbox turn");
176
+ if (item.done && item.record.state === "completed") {
177
+ if (disposition !== "complete")
178
+ throw new Error("A completed delivery cannot be deferred");
179
+ return;
180
+ }
181
+ if (item.done && disposition === "defer")
182
+ return;
183
+ item.record = await store.transitionClaimed(item.record.acceptance_id, item.claim, disposition === "complete" ? "completed" : "accepted", undefined, disposition === "complete" ? "" : undefined, disposition === "defer" ? { deferred: true, clear_execution: true, execution_error: null } : { execution_id: executionId, deferred: false, execution_error: null });
184
+ item.done = true;
185
+ })
186
+ });
125
187
  const first = claimed[0].record;
126
- result = await host.wake({ token, agentId: agent.id, event: {
188
+ if (agent.recording) {
189
+ await recordWakeMoment(agent, agent.attention === undefined ? first.delivery_id : executionId, claimed.map(({ record }) => record), { ...this.options.recordingOptionsForTest, verifyStore: () => verifyHostRecordingStore(host, agent.id) });
190
+ }
191
+ result = await wakeFromDurableInbox(host, { token, agentId: agent.id, event: {
127
192
  version: "noopolis.daimon.wake.v1", id: agent.attention === undefined ? first.delivery_id : executionId, kind: first.event.kind,
128
- occurredAt: first.event.occurred_at,
193
+ occurredAt: messages[0].occurred_at,
129
194
  text: agent.attention === undefined ? first.event.text : inboxPrompt(messages, agent.engine.kind, agent.attention.maxBatchBytes)
130
195
  } });
131
196
  }
@@ -152,9 +217,21 @@ export class AttentionDispatcher {
152
217
  await store.transitionClaimed(item.record.acceptance_id, item.claim, "accepted", result.code);
153
218
  }
154
219
  else if (agent.attention !== undefined) {
155
- // Successful reading is not completion. A failed execution also keeps
156
- // unfinished deliveries, and its execution id for idempotent retry.
157
- await store.transitionClaimed(item.record.acceptance_id, item.claim, "accepted", undefined, undefined, { deferred: true, clear_execution: result.status === "completed", execution_error: executionError });
220
+ // Successful reading is not completion: an undisposed delivery of a
221
+ // completed execution waits, deferred, for new input.
222
+ //
223
+ // A failed execution is retried ONCE, at once, under a fresh execution
224
+ // id. Keeping its id made every retry the same broker turn — a sealed
225
+ // failure replays forever and a changed prompt is a turn conflict — and
226
+ // deferring it left work a turn died on (a token ceiling after a
227
+ // validated revision) waiting for mail that might never come. The
228
+ // record's `execution_error` is the bound: it is set by the failure and
229
+ // cleared only by a disposition or a completed execution, so a delivery
230
+ // that fails again is deferred like any other.
231
+ const retry = result.status === "failed" && item.record.execution_error === undefined;
232
+ await store.transitionClaimed(item.record.acceptance_id, item.claim, "accepted", undefined, undefined, { deferred: !retry, clear_execution: result.status !== "rejected", execution_error: executionError });
233
+ if (retry)
234
+ console.error(`daimon: wake requeued once agent=${agent.id} delivery=${item.record.delivery_id} after ${sanitizeExecutionError(executionError ?? "engine_failed")}`);
158
235
  }
159
236
  else if (result.status === "completed")
160
237
  await store.transitionClaimed(item.record.acceptance_id, item.claim, "completed", undefined, result.text);
@@ -179,7 +256,7 @@ export class AttentionDispatcher {
179
256
  void this.options.host.stop().catch(() => undefined);
180
257
  }
181
258
  async waitUntil(timestamp) {
182
- if (this.stopping)
259
+ if (this.stopping || this.paused)
183
260
  return;
184
261
  await new Promise((resolve) => {
185
262
  const finish = () => { clearTimeout(timer); this.waiters.delete(finish); resolve(); };
@@ -188,6 +265,23 @@ export class AttentionDispatcher {
188
265
  });
189
266
  }
190
267
  }
268
+ /**
269
+ * A delivery past `STALE_QUEUED_DELIVERY_MS` is stopped `queued_wake_stopped`
270
+ * where the dispatcher meets it, and the stop is logged: an expiry nobody can
271
+ * see reads, in every artifact, like work that was never sent. Stopping goes
272
+ * through the store's accepted-only seam, so a delivery claimed in the meantime
273
+ * is left alone; a failure to stop is logged and retried on the next drain.
274
+ */
275
+ async function expireStaleDelivery(store, record) {
276
+ try {
277
+ const stopped = await store.transitionAcceptedToStopped(record.acceptance_id, "queued_wake_stopped");
278
+ if (stopped.state === "stopped")
279
+ console.error(`daimon: wake expired agent=${record.agent_id} delivery=${record.delivery_id} accepted_at=${record.accepted_at} unhandled past ${STALE_QUEUED_DELIVERY_MS / 3_600_000}h; stopped queued_wake_stopped${record.execution_error === undefined ? "" : ` (last failure: ${record.execution_error})`}`);
280
+ }
281
+ catch (error) {
282
+ console.error(`daimon: wake expiry failed agent=${record.agent_id} delivery=${record.delivery_id}: ${error instanceof Error ? error.message : "unknown error"}`);
283
+ }
284
+ }
191
285
  export function selectBatch(records, agent, revisitDeferred) {
192
286
  const eligible = records.filter((record) => !record.deferred || revisitDeferred);
193
287
  const first = eligible[0];
@@ -139,6 +139,11 @@ async function handleRequest(request, response, host, control, expectedToken) {
139
139
  const availability = await control.availability(expectedToken);
140
140
  return availability === undefined ? respond(response, 404, { error: "not_found" }) : respond(response, 200, availability);
141
141
  }
142
+ if (control !== undefined && request.method === "POST" && (url.pathname === "/v2/drain" || url.pathname === "/v2/resume")) {
143
+ assertQuery(url, []);
144
+ const availability = url.pathname === "/v2/drain" ? await control.drain(expectedToken) : await control.resume(expectedToken);
145
+ return availability === undefined ? respond(response, 404, { error: "not_found" }) : respond(response, 200, availability);
146
+ }
142
147
  if (control !== undefined && request.method === "GET" && url.pathname === "/v2/activity") {
143
148
  assertQuery(url, []);
144
149
  const activity = await control.activityV2(expectedToken);