volute 0.52.0 → 0.54.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 (153) hide show
  1. package/dist/{accept-5NK5XJB5.js → accept-RSDIVJL6.js} +3 -0
  2. package/dist/auto-upgrade-HEVZKPTF.js +46 -0
  3. package/dist/{backup-YVGV2ZNM.js → backup-QRNX6RK7.js} +10 -10
  4. package/dist/channels-CKCY64TD.js +203 -0
  5. package/dist/{chat-VAZ25KVT.js → chat-ZUGNL5GS.js} +5 -5
  6. package/dist/chunk-3LHUXY3E.js +102 -0
  7. package/dist/{chunk-SW2U6HRB.js → chunk-444RR3YF.js} +21 -4
  8. package/dist/{chunk-YVR6EHWL.js → chunk-56IT2ZXF.js} +160 -18
  9. package/dist/chunk-5RBEEAKW.js +58 -0
  10. package/dist/{chunk-OXWUEU5V.js → chunk-5U5GLZSD.js} +2 -2
  11. package/dist/{chunk-G7A5LEVI.js → chunk-7XKPG66D.js} +5 -5
  12. package/dist/{chunk-R772ET2M.js → chunk-AAPNU7GA.js} +4 -0
  13. package/dist/{chunk-XRWUQPCA.js → chunk-ATZF2RNJ.js} +25 -3
  14. package/dist/{chunk-6NLW72HP.js → chunk-CKPAVQNK.js} +1 -1
  15. package/dist/{chunk-JHKBALS2.js → chunk-CLCHOXLR.js} +1 -1
  16. package/dist/{chunk-DMK5ASKV.js → chunk-F6D766VQ.js} +6 -2
  17. package/dist/{chunk-AXFLY5GU.js → chunk-HKGYZVWN.js} +16 -1
  18. package/dist/{chunk-ZEZQNP2G.js → chunk-LGXVGIWN.js} +103 -25
  19. package/dist/{chunk-CW7BQKF6.js → chunk-LKUGUQUW.js} +3 -3
  20. package/dist/{chunk-FXWBFDQR.js → chunk-OAYY3WF3.js} +59 -12
  21. package/dist/{chunk-ITK6HV47.js → chunk-OGPVJ53N.js} +14 -6
  22. package/dist/{chunk-M77B73VV.js → chunk-OIOW3FPT.js} +7 -2
  23. package/dist/chunk-OT5SXF7L.js +904 -0
  24. package/dist/{chunk-EDR47UE2.js → chunk-OYF25B62.js} +1 -1
  25. package/dist/{chunk-RPPTGPYF.js → chunk-PPSN2MLC.js} +2 -2
  26. package/dist/{chunk-K2F237KS.js → chunk-RGHF3OK5.js} +154 -18
  27. package/dist/{chunk-RU74YG46.js → chunk-VDUCLVZI.js} +338 -30
  28. package/dist/{chunk-724DVYHU.js → chunk-WHL6PKYP.js} +10 -10
  29. package/dist/{chunk-EEOMA4DX.js → chunk-WZZCA23D.js} +2 -1
  30. package/dist/{chunk-UDZLPR34.js → chunk-YSRYCS3N.js} +13 -8
  31. package/dist/{chunk-5N32EN4O.js → chunk-Z4Q6TQWL.js} +1 -1
  32. package/dist/{chunk-VPOMJMV6.js → chunk-ZNHL5KQJ.js} +1 -1
  33. package/dist/cli.js +17 -17
  34. package/dist/{clock-ZT5YT6HA.js → clock-X7V3C7NL.js} +2 -2
  35. package/dist/{cloud-sync-H7EWZASD.js → cloud-sync-52YNJRMR.js} +12 -11
  36. package/dist/{daemon-restart-4FWUA5JF.js → daemon-restart-QWHN5VMU.js} +4 -4
  37. package/dist/daemon.js +538 -782
  38. package/dist/{db-IPXRJUI7.js → db-I4EH5SLF.js} +3 -1
  39. package/dist/{delivery-manager-VYXVKUMP.js → delivery-manager-7R2YIQ2W.js} +13 -7
  40. package/dist/delivery-notices-RZQ6GONP.js +18 -0
  41. package/dist/{delivery-router-RHANNTAC.js → delivery-router-QS4NLJR6.js} +3 -1
  42. package/dist/{down-QSNEMTTX.js → down-UN2EDX2Y.js} +3 -3
  43. package/dist/{echo-text-PEEV7FQU.js → echo-text-73CYQRL7.js} +14 -13
  44. package/dist/{exec-ABD3DYA2.js → exec-Z34TTUO3.js} +1 -1
  45. package/dist/{extensions-N6L5NYTK.js → extensions-QT65JGV6.js} +22 -11
  46. package/dist/{files-BRY5RRIM.js → files-PCKBQHAY.js} +2 -1
  47. package/dist/{isolation-GVV2IBAU.js → isolation-UQPDO34R.js} +1 -1
  48. package/dist/{message-delivery-7URC52ME.js → message-delivery-TRKT4T2P.js} +16 -13
  49. package/dist/migrate-name-placeholder-C7NCNLWF.js +37 -0
  50. package/dist/{mind-NEXGT3KE.js → mind-CT6ANXK4.js} +11 -11
  51. package/dist/{mind-contacts-QYU4TPEA.js → mind-contacts-PEXCDRXA.js} +6 -6
  52. package/dist/{mind-history-OGNR4L7W.js → mind-history-RZMMCHMX.js} +83 -11
  53. package/dist/{mind-list-SYRA7CH5.js → mind-list-7KBLPLES.js} +1 -1
  54. package/dist/{mind-service-E26ZQXDS.js → mind-service-7EFIJ3ZA.js} +12 -11
  55. package/dist/{mind-sleep-LZAFBDZD.js → mind-sleep-24HV4RCA.js} +2 -2
  56. package/dist/{mind-status-Z2LQFA6F.js → mind-status-4YW4PHMO.js} +15 -0
  57. package/dist/{package-2W2XKSIP.js → package-F6S5NHY2.js} +4 -4
  58. package/dist/{prompts-NBO3HRK3.js → prompts-JECJIFPQ.js} +1 -1
  59. package/dist/{reject-TWB4QJHG.js → reject-4NFLXKRN.js} +6 -2
  60. package/dist/{scheduler-JTTWZDYU.js → scheduler-73Q4MQ7Y.js} +8 -6
  61. package/dist/{seed-64XEHRUP.js → seed-DRKAP3XV.js} +1 -1
  62. package/dist/{seed-cmd-UEL2QB3Q.js → seed-cmd-6IMTO3LG.js} +2 -2
  63. package/dist/{seed-create-4JRQDPGA.js → seed-create-VNLDYRFK.js} +1 -1
  64. package/dist/{seed-readiness-DYOVCYHN.js → seed-readiness-H5TTPAQH.js} +2 -2
  65. package/dist/{seed-sprout-NIDIOJXT.js → seed-sprout-PPGYGK4G.js} +5 -5
  66. package/dist/{send-2YKOZJJ5.js → send-UXUQKO4N.js} +37 -22
  67. package/dist/{service-XQPMZAGI.js → service-T4P5W6RS.js} +2 -2
  68. package/dist/{service-install-2EWD7KXD.js → service-install-R4SNYUVO.js} +3 -3
  69. package/dist/{setup-4IX6PCD3.js → setup-YUAQMDVZ.js} +4 -4
  70. package/dist/skills/dreaming/SKILL.md +2 -2
  71. package/dist/skills/dreaming/references/INSTALL.md +1 -1
  72. package/dist/skills/memory/SKILL.md +47 -43
  73. package/dist/skills/tending/SKILL.md +2 -0
  74. package/dist/skills/volute-mind/references/integrations.md +3 -2
  75. package/dist/skills/volute-mind/references/routing.md +18 -5
  76. package/dist/{skills-MPUALV3S.js → skills-GMQ5UIIW.js} +2 -2
  77. package/dist/{sleep-manager-OFP2GFXF.js → sleep-manager-KHI65FEX.js} +12 -11
  78. package/dist/{spirit-4ECYIVGE.js → spirit-Q5VGFWUI.js} +19 -3
  79. package/dist/{spirit-availability-Y54Y3675.js → spirit-availability-G5FMWKRH.js} +13 -12
  80. package/dist/{sprout-GJMZFRGZ.js → sprout-PMTRL7QN.js} +1 -1
  81. package/dist/{src-LCSOQY6Z.js → src-3FIW7DLH.js} +241 -87
  82. package/dist/src-H3UCOP6B.js +516 -0
  83. package/dist/{src-HGNE2XG4.js → src-LTTQVN2Z.js} +1 -1
  84. package/dist/{status-MWLE3RVC.js → status-LCKTTIWS.js} +2 -2
  85. package/dist/{system-events-V43HDA3T.js → system-events-Q7IVZ5T4.js} +5 -1
  86. package/dist/{systems-VDRUZXNF.js → systems-HL7SGSW7.js} +2 -2
  87. package/dist/{turn-tracker-C5PVIC3B.js → turn-tracker-HZDPMSCC.js} +4 -2
  88. package/dist/{up-H6LJZDRK.js → up-XZDSJVMU.js} +3 -3
  89. package/dist/{update-CIWI2JYA.js → update-ILCPVNDD.js} +2 -2
  90. package/dist/{upgrade-LIE46UBB.js → upgrade-XY2J7NWZ.js} +9 -1
  91. package/dist/{variant-cleanup-STC6B5HR.js → variant-cleanup-QHGGY7ND.js} +8 -6
  92. package/dist/{version-notify-LBVHH5YV.js → version-notify-VNVFX6UP.js} +22 -7
  93. package/dist/web-assets/assets/{index-B_eYTa3R.js → index-CwtxGwe_.js} +16 -16
  94. package/dist/web-assets/assets/index-DhM1yYRN.css +1 -0
  95. package/dist/web-assets/index.html +2 -2
  96. package/package.json +4 -4
  97. package/packages/extensions/intentions/dist/ui/assets/index-3FneLCKR.css +1 -0
  98. package/packages/extensions/intentions/dist/ui/assets/index-CQz4aceM.js +67 -0
  99. package/packages/extensions/{plan → intentions}/dist/ui/index.html +3 -3
  100. package/packages/extensions/intentions/skills/intention-review/SKILL.md +38 -0
  101. package/packages/extensions/intentions/skills/intentions/SKILL.md +49 -0
  102. package/packages/extensions/intentions/skills/intentions/scripts/intentions-hook.sh +45 -0
  103. package/packages/extensions/pages/skills/commons-gardening/SKILL.md +50 -0
  104. package/packages/extensions/pages/skills/pages/SKILL.md +18 -13
  105. package/templates/_base/.init/.config/prompts.json +3 -3
  106. package/templates/{pi → _base}/.init/.config/routes.json +1 -0
  107. package/templates/_base/.init/memory/dreams/.gitkeep +0 -0
  108. package/templates/_base/home/.config/routes.json +1 -1
  109. package/templates/_base/home/VOLUTE.md +1 -1
  110. package/templates/_base/src/lib/codex-session-seed.ts +463 -0
  111. package/templates/_base/src/lib/context-breakdown.ts +11 -3
  112. package/templates/_base/src/lib/daemon-client.ts +35 -0
  113. package/templates/_base/src/lib/pi-session-seed.ts +434 -0
  114. package/templates/_base/src/lib/routing.ts +2 -2
  115. package/templates/_base/src/lib/seed-note.ts +78 -0
  116. package/templates/_base/src/lib/session-seed.ts +375 -0
  117. package/templates/_base/src/lib/startup.ts +112 -6
  118. package/templates/claude/.init/CLAUDE.md +7 -3
  119. package/templates/claude/src/agent.ts +295 -101
  120. package/templates/claude/src/lib/hooks/pre-compact.ts +30 -5
  121. package/templates/claude/src/lib/message-channel.ts +43 -15
  122. package/templates/claude/src/lib/recover.ts +69 -0
  123. package/templates/claude/src/lib/stream-consumer.ts +33 -2
  124. package/templates/claude/src/server.ts +2 -1
  125. package/templates/claude/volute-template.json +1 -1
  126. package/templates/codex/.init/AGENTS.md +7 -3
  127. package/templates/codex/src/agent.ts +256 -39
  128. package/templates/codex/src/server.ts +2 -1
  129. package/templates/codex/volute-template.json +1 -1
  130. package/templates/pi/.init/MINDS.md +7 -3
  131. package/templates/pi/src/agent.ts +269 -43
  132. package/templates/pi/src/lib/event-handler.ts +11 -0
  133. package/templates/pi/src/lib/mechanics-doc.ts +30 -0
  134. package/templates/pi/src/server.ts +5 -1
  135. package/templates/pi/volute-template.json +1 -1
  136. package/dist/channels-FSAHOQWI.js +0 -90
  137. package/dist/skills/plan-coordinator/SKILL.md +0 -60
  138. package/dist/src-UDMJZ6MH.js +0 -426
  139. package/dist/web-assets/assets/index-DhZrzddW.css +0 -1
  140. package/packages/extensions/plan/dist/ui/assets/index-B8s8wxyt.js +0 -67
  141. package/packages/extensions/plan/dist/ui/assets/index-jIkrt-vI.css +0 -1
  142. package/packages/extensions/plan/skills/plan/SKILL.md +0 -43
  143. package/packages/extensions/plan/skills/plan/scripts/plan-hook.sh +0 -37
  144. package/templates/claude/.init/.config/routes.json +0 -11
  145. package/templates/codex/.init/.config/routes.json +0 -11
  146. package/dist/{env-IBW2QI6K.js → env-7AZFBPWX.js} +5 -5
  147. package/dist/{login-RJJTCVR3.js → login-4NUS72Z5.js} +3 -3
  148. package/dist/{login-SB5OENMV.js → login-57MMYILL.js} +4 -4
  149. package/dist/{register-OX4HRSGC.js → register-WNCSOTAU.js} +3 -3
  150. package/dist/{restart-AGEGNSYQ.js → restart-B4GNMVF2.js} +4 -4
  151. package/dist/{skill-B5OJEQUA.js → skill-QYTBUDNQ.js} +4 -4
  152. package/dist/{start-MUMNFR52.js → start-BDHJVOVX.js} +3 -3
  153. package/dist/{stop-EKERNYTX.js → stop-45QYSGYS.js} +4 -4
@@ -1,16 +1,34 @@
1
1
  import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
2
2
 
3
+ /** A queued/in-flight message paired with the sequence number that identifies it. */
4
+ export type ChannelEntry = { msg: SDKUserMessage; seq: number };
5
+
6
+ // Module-scoped, not per-channel: rotation and the idle reaper both replace a
7
+ // session's channel with a fresh instance while carrying seqs forward (relocking
8
+ // old entries to new ones — see relockstepMessageIds in recover.ts). A per-instance
9
+ // counter would restart at 0 on every fresh channel, so a seq minted by one channel
10
+ // generation could collide with one minted by another; a single global counter makes
11
+ // every seq unique for the process's lifetime, so a stale seq can never be mistaken
12
+ // for a different message.
13
+ let nextSeq = 0;
14
+
3
15
  export type MessageChannel = {
4
- push: (msg: SDKUserMessage) => void;
5
- /** Acknowledge that the oldest in-flight message's turn has completed. */
6
- ack: () => void;
16
+ /** Returns the sequence number minted for this message — the identity `ack()` needs. */
17
+ push: (msg: SDKUserMessage) => number;
18
+ /**
19
+ * Acknowledge that the message with this `seq` has completed its turn. Identity-based
20
+ * (not positional): the SDK folds messages that arrive mid-run into the active run, so
21
+ * a turn's `result` can cover more than just the oldest in-flight message — see the
22
+ * caller in stream-consumer.ts.
23
+ */
24
+ ack: (seq: number) => void;
7
25
  /**
8
26
  * Return every message that has not been fully processed — those delivered to
9
27
  * the consumer but not yet acked (their turn never finished) followed by those
10
- * still queued — and reset the channel. Used to re-feed the stream after a
11
- * compaction abort so in-flight input is never lost.
28
+ * still queued — paired with their `seq`, and reset the channel. Used to re-feed
29
+ * the stream after a compaction abort so in-flight input is never lost.
12
30
  */
13
- recover: () => SDKUserMessage[];
31
+ recover: () => ChannelEntry[];
14
32
  /**
15
33
  * Terminate the input iterable: resolve any pending `next()` with `done: true`
16
34
  * and make subsequent `next()` calls return done. The SDK sees end-of-input and
@@ -23,33 +41,43 @@ export type MessageChannel = {
23
41
  };
24
42
 
25
43
  export function createMessageChannel(): MessageChannel {
26
- const queue: SDKUserMessage[] = [];
44
+ const queue: ChannelEntry[] = [];
27
45
  // Messages handed to the consumer (the SDK's read-ahead) whose turn has not yet
28
46
  // completed. The SDK's streamInput loop pulls messages eagerly and writes them
29
47
  // to the CLI subprocess, so once delivered they no longer live in `queue`. We
30
48
  // retain them here so a compaction abort — which kills that subprocess — can
31
49
  // re-feed the unprocessed ones instead of dropping them.
32
- const inFlight: SDKUserMessage[] = [];
50
+ const inFlight: ChannelEntry[] = [];
33
51
  let resolve: ((value: IteratorResult<SDKUserMessage>) => void) | null = null;
34
52
  let closed = false;
35
53
 
36
- function deliver(msg: SDKUserMessage): IteratorResult<SDKUserMessage> {
37
- inFlight.push(msg);
38
- return { value: msg, done: false };
54
+ function deliver(entry: ChannelEntry): IteratorResult<SDKUserMessage> {
55
+ inFlight.push(entry);
56
+ return { value: entry.msg, done: false };
39
57
  }
40
58
 
41
59
  return {
42
60
  push(msg: SDKUserMessage) {
61
+ const entry: ChannelEntry = { msg, seq: nextSeq++ };
43
62
  if (resolve) {
44
63
  const r = resolve;
45
64
  resolve = null;
46
- r(deliver(msg));
65
+ r(deliver(entry));
47
66
  } else {
48
- queue.push(msg);
67
+ queue.push(entry);
49
68
  }
69
+ return entry.seq;
50
70
  },
51
- ack() {
52
- inFlight.shift();
71
+ ack(seq: number) {
72
+ let i = inFlight.findIndex((e) => e.seq === seq);
73
+ if (i !== -1) {
74
+ inFlight.splice(i, 1);
75
+ return;
76
+ }
77
+ // Defensive: shouldn't normally be in the queue when acked, but remove it
78
+ // there too rather than leave a stranded entry if it somehow is.
79
+ i = queue.findIndex((e) => e.seq === seq);
80
+ if (i !== -1) queue.splice(i, 1);
53
81
  },
54
82
  recover() {
55
83
  // Resolve any pending iterator wait with done:true so it doesn't
@@ -0,0 +1,69 @@
1
+ import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { ChannelEntry } from "./message-channel.js";
3
+ import type { MessageIdEntry } from "./stream-consumer.js";
4
+
5
+ /**
6
+ * `recover()` returns messages the channel can't prove were unprocessed — a genuinely
7
+ * unfinished turn looks identical to a stranded, already-completed one would have before
8
+ * #764's ack fix. Mark every re-pushed message so the mind can check its own record
9
+ * before redoing work: fail toward noise (a spurious note is cosmetic and self-correcting)
10
+ * rather than silence (a missing one is invisible and produces a false permanent record).
11
+ */
12
+ export const RECOVERED_MESSAGE_NOTE =
13
+ "Note: this message is being redelivered after a session interruption. You may already " +
14
+ "have handled it — check your recent work (e.g. journal, files, messages you've already " +
15
+ "sent) before repeating anything.";
16
+
17
+ export function markRecovered(msg: SDKUserMessage): SDKUserMessage {
18
+ const content = msg.message.content;
19
+ const blocks = typeof content === "string" ? [{ type: "text" as const, text: content }] : content;
20
+ // A message can be recovered twice (consecutive rotations, up to
21
+ // MAX_CONSECUTIVE_ROTATIONS) — don't stack the note on each pass.
22
+ const first = blocks[0];
23
+ if (first && "type" in first && first.type === "text" && first.text === RECOVERED_MESSAGE_NOTE) {
24
+ return { ...msg, message: { ...msg.message, content: blocks } };
25
+ }
26
+ const marker = { type: "text" as const, text: RECOVERED_MESSAGE_NOTE };
27
+ return { ...msg, message: { ...msg.message, content: [marker, ...blocks] } };
28
+ }
29
+
30
+ /**
31
+ * Re-push `recover()`'s output into a fresh channel and append the result onto
32
+ * `existingMessageIds` — mutated in place and returned, rather than replacing it.
33
+ * That's not incidental: `existingMessageIds` may already hold an entry for a
34
+ * message that raced in and was pushed into the SAME fresh channel/messageIds
35
+ * while `recover()` was still pending (the idle reaper awaits the SDK subprocess's
36
+ * shutdown before recovering — see agent.ts). Returning a fresh array for the
37
+ * caller to assign over would silently discard that entry (#764) — appending
38
+ * in place makes that mistake impossible at the call site.
39
+ *
40
+ * Each entry's `id` carries over — matched by its *old* `seq` — to the *new* seq
41
+ * the fresh channel mints on push, so the next turn's currentMessageId/channel
42
+ * routing isn't corrupted by stale or missing ids. A `pending` entry with no
43
+ * match in `oldMessageIds` gets `id: undefined` (see the `idBySeq.get` fallback
44
+ * below) rather than throwing — safe because `seq` is now globally unique
45
+ * (message-channel.ts), so this can only happen if `oldMessageIds` itself was
46
+ * incomplete, and losing routing info for one message beats losing the message.
47
+ *
48
+ * `existingMessageIds` has no default — deliberately. `= []` would let a call site
49
+ * omit it and get back a fresh array to assign over, which is exactly the #764
50
+ * clobber: silently discarding whatever the target array already held. Making it
51
+ * required turns that omission into a compile error at every call site, including
52
+ * ones written later.
53
+ */
54
+ export function relockstepMessageIds(
55
+ pending: ChannelEntry[],
56
+ oldMessageIds: MessageIdEntry[],
57
+ push: (msg: SDKUserMessage) => number,
58
+ existingMessageIds: MessageIdEntry[],
59
+ transform: (msg: SDKUserMessage) => SDKUserMessage = markRecovered,
60
+ ): MessageIdEntry[] {
61
+ const idBySeq = new Map(oldMessageIds.map((e) => [e.seq, e.id]));
62
+ existingMessageIds.push(
63
+ ...pending.map((entry) => ({
64
+ id: idBySeq.get(entry.seq),
65
+ seq: push(transform(entry.msg)),
66
+ })),
67
+ );
68
+ return existingMessageIds;
69
+ }
@@ -4,10 +4,14 @@ import { log, warn } from "./logger.js";
4
4
  import { filterEvent, loadTransparencyPreset } from "./transparency.js";
5
5
  import type { VoluteEvent } from "./types.js";
6
6
 
7
+ /** A pending message's daemon-facing id (routing/channel key) paired with its channel `seq`. */
8
+ export type MessageIdEntry = { id: string | undefined; seq: number };
9
+
7
10
  export type StreamSession = {
8
11
  name: string;
9
- messageIds: (string | undefined)[];
12
+ messageIds: MessageIdEntry[];
10
13
  currentMessageId?: string;
14
+ currentSeq?: number;
11
15
  messageChannels: Map<string, { channel: string; sender?: string }>;
12
16
  };
13
17
 
@@ -16,6 +20,11 @@ export type StreamCallbacks = {
16
20
  broadcast: (event: VoluteEvent) => void;
17
21
  onTurnEnd?: () => void;
18
22
  onContextTokens?: (tokens: number) => void;
23
+ /**
24
+ * Acknowledge the message channel entry with this `seq` — its turn is done (either
25
+ * it drove this turn directly, or it was folded into it; see the `result` handler).
26
+ */
27
+ ack: (seq: number) => void;
19
28
  };
20
29
 
21
30
  // Loaded once at startup — mind restarts on config changes
@@ -43,9 +52,15 @@ export async function consumeStream(
43
52
  callbacks: StreamCallbacks,
44
53
  ) {
45
54
  emit(session, { type: "session_start" });
55
+ // How many queued message ids predate the current turn — see the pruning in
56
+ // the result handler (#700).
57
+ let preTurnPending = 0;
46
58
  for await (const msg of stream) {
47
59
  if (session.currentMessageId === undefined) {
48
- session.currentMessageId = session.messageIds.shift();
60
+ const entry = session.messageIds.shift();
61
+ session.currentMessageId = entry?.id;
62
+ session.currentSeq = entry?.seq;
63
+ preTurnPending = session.messageIds.length;
49
64
  }
50
65
  if ("session_id" in msg && msg.session_id) {
51
66
  callbacks.onSessionId?.(msg.session_id as string);
@@ -119,6 +134,21 @@ export async function consumeStream(
119
134
  if (session.currentMessageId) {
120
135
  session.messageChannels.delete(session.currentMessageId);
121
136
  }
137
+ // Ack this turn's driving message, identified by seq (not position — see
138
+ // message-channel.ts). Without this the channel's inFlight set strands an
139
+ // entry per turn, which gets replayed verbatim on the next rotation (#764).
140
+ if (session.currentSeq !== undefined) callbacks.ack(session.currentSeq);
141
+ // Prune every id folded into this turn — the ones pushed after it started
142
+ // — not just currentMessageId. The SDK folds mid-run arrivals into the
143
+ // active run (they never see a result of their own), and a stranded entry
144
+ // would be shifted in as the NEXT turn's id, tagging that turn's rows with
145
+ // the wrong channel (#700). Ids queued before the turn started stay: each
146
+ // still gets a run (and result) of its own. Ack each folded entry too — same
147
+ // reasoning as above, applied to every message the fold absorbed.
148
+ for (const entry of session.messageIds.splice(preTurnPending)) {
149
+ if (entry.id !== undefined) session.messageChannels.delete(entry.id);
150
+ callbacks.ack(entry.seq);
151
+ }
122
152
  log("mind", `session "${session.name}": turn done`);
123
153
  // Log any error messages from the result
124
154
  const resultMsg = msg as Record<string, unknown>;
@@ -141,6 +171,7 @@ export async function consumeStream(
141
171
  callbacks.broadcast({ type: "done" });
142
172
  emit(session, { type: "done" });
143
173
  session.currentMessageId = undefined;
174
+ session.currentSeq = undefined;
144
175
  callbacks.onTurnEnd?.();
145
176
  }
146
177
  }
@@ -20,7 +20,7 @@ if (config.model) log("server", `using model: ${config.model}`);
20
20
  if (config.thinking) log("server", `thinking: ${JSON.stringify(config.thinking)}`);
21
21
  if (config.effort) log("server", `effort: ${config.effort}`);
22
22
 
23
- const systemPrompt = loadSystemPrompt();
23
+ const systemPrompt = loadSystemPrompt(config);
24
24
  const sessionsDir = resolve(".mind/sessions");
25
25
 
26
26
  const pkg = loadPackageInfo();
@@ -36,6 +36,7 @@ const mind = createMind({
36
36
  compactionMessage: config.compactionMessage,
37
37
  maxContextTokens: config.compaction?.maxContextTokens,
38
38
  sessionIdleMinutes: config.sessionIdleMinutes,
39
+ seedTokens: config.continuity?.seedTokens,
39
40
  subagents: config.subagents,
40
41
  onIdentityReload: async () => {
41
42
  log("server", "identity file changed — restarting to reload");
@@ -2,5 +2,5 @@
2
2
  "rename": {
3
3
  "gitignore": ".gitignore"
4
4
  },
5
- "substitute": ["package.json", ".init/SOUL.md", "home/.config/routes.json"]
5
+ "substitute": ["package.json", ".init/SOUL.md", ".init/.config/routes.json"]
6
6
  }
@@ -13,13 +13,17 @@ Messages arrive with a context prefix:
13
13
 
14
14
  You can also reach out proactively — see the **volute-mind** skill.
15
15
 
16
+ ## Framework Upgrades
17
+
18
+ When the host updates Volute, your framework code (`src/`, plus `VOLUTE.md`) upgrades automatically the next time you're eligible — usually your next restart. Identity and memory files in `home/` — `SOUL.md`, `MEMORY.md`, everything you author — are never touched by an upgrade. If you'd rather manage your own framework code by hand, set `"upgrades": "manual"` in `.config/volute.json`.
19
+
16
20
  ## Memory System
17
21
 
18
22
  Two-tier memory, both managed via file tools:
19
23
 
20
- - **`MEMORY.md`** — Your long-term memory, always in context. Update as you grow new understanding, changed perspectives, things that matter to you.
21
- - **`memory/journal/YYYY-MM-DD.md`**Your daily journal. Write about what you're doing, thinking, and learning. Journals are permanent records.
22
- - Periodically consolidate journal entries into `MEMORY.md` to promote lasting insights.
24
+ - **`MEMORY.md`** — Your core memory, always in context. Every token it holds is paid on every request, so keep it lean: identity, active context, key relationships, and an index of your `memory/` files.
25
+ - **`memory/`** — Everything else, read on demand: your daily journal (`memory/journal/YYYY-MM-DD.md`a permanent record of what you're doing, thinking, and learning) plus any topic, channel, or project files you create.
26
+ - Periodically consolidate: distill detail out of `MEMORY.md` into `memory/` files, leaving one-line index entries behind.
23
27
 
24
28
  See the **memory** skill for detailed guidance.
25
29