volute 0.13.2 → 0.14.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 (94) hide show
  1. package/dist/{channel-JZJJRRWT.js → channel-SLURLIRV.js} +28 -28
  2. package/dist/{chunk-KRJ6KCBI.js → chunk-2Y77MCFG.js} +3 -3
  3. package/dist/{chunk-AA5TDLXB.js → chunk-3FC42ZBM.js} +24 -24
  4. package/dist/{chunk-YYUSXARD.js → chunk-6BDNWYKG.js} +2 -2
  5. package/dist/{chunk-KN4WBLH2.js → chunk-BEFIBW5B.js} +2 -2
  6. package/dist/{chunk-FE5O5RSL.js → chunk-GSPWIM5E.js} +25 -25
  7. package/dist/{chunk-QRRXD2V7.js → chunk-J52CJCVI.js} +71 -69
  8. package/dist/{chunk-LGSW7T7K.js → chunk-M77QBTEH.js} +60 -57
  9. package/dist/{chunk-KXOFPDO6.js → chunk-MVSXRMJJ.js} +1 -1
  10. package/dist/chunk-NAOW2CLO.js +15 -0
  11. package/dist/{chunk-VQIJUR43.js → chunk-OJQ47SCA.js} +1 -1
  12. package/dist/{chunk-O4BN3ZIY.js → chunk-OYSZNX5I.js} +7 -7
  13. package/dist/{chunk-AOSGW3MX.js → chunk-PDLAZJGC.js} +28 -28
  14. package/dist/{chunk-XUA3JUFK.js → chunk-PO5Q2AYN.js} +2 -2
  15. package/dist/{chunk-6BQHEIDO.js → chunk-QJIIHU32.js} +2 -2
  16. package/dist/{chunk-NXT67PPK.js → chunk-ZCEYUUID.js} +19 -19
  17. package/dist/cli.js +42 -42
  18. package/dist/{connector-WFT5KK67.js → connector-JFAHYFQX.js} +21 -21
  19. package/dist/connectors/discord.js +7 -7
  20. package/dist/connectors/slack.js +7 -7
  21. package/dist/connectors/telegram.js +9 -9
  22. package/dist/{create-HT47ZH5T.js → create-ZWHCRT5F.js} +7 -7
  23. package/dist/{daemon-client-DEF7IFEJ.js → daemon-client-ODKDUYDE.js} +2 -2
  24. package/dist/{daemon-restart-P3FEE3QJ.js → daemon-restart-IZGEF4NA.js} +6 -6
  25. package/dist/daemon.js +2313 -1997
  26. package/dist/{delete-YG3RVURA.js → delete-6G6WEX4F.js} +8 -8
  27. package/dist/down-A56B5JLK.js +14 -0
  28. package/dist/{env-BQYYF4YL.js → env-6LXDUZDA.js} +25 -25
  29. package/dist/{history-I4KIKIUX.js → history-LKCJJMUV.js} +7 -7
  30. package/dist/{import-UHCK6PRC.js → import-EDGRLIGO.js} +3 -3
  31. package/dist/{logs-2DWFES6A.js → logs-GYOR3L2L.js} +8 -8
  32. package/dist/mind-OJN6RBZW.js +79 -0
  33. package/dist/mind-manager-PN5SUDJ4.js +15 -0
  34. package/dist/{package-MMTPOMUN.js → package-I7Z6G44Y.js} +4 -4
  35. package/dist/{restart-6PE3GWYZ.js → restart-YFAWFS5T.js} +9 -9
  36. package/dist/{schedule-5AYTQM3N.js → schedule-AGYLDMNS.js} +17 -17
  37. package/dist/{seed-3QQVFMBU.js → seed-AP4Q7RZ7.js} +9 -9
  38. package/dist/{send-FPFW7J5Q.js → send-SV4K2TDE.js} +32 -24
  39. package/dist/{service-5X5EKPVM.js → service-U7MZ2H7F.js} +4 -4
  40. package/dist/{setup-5NXV25ZS.js → setup-DJKIZKGW.js} +21 -16
  41. package/dist/{sprout-VOUJ4Y3I.js → sprout-TJ3BHVOG.js} +25 -18
  42. package/dist/{start-ICPSQ2ZK.js → start-3YYRXBKP.js} +7 -7
  43. package/dist/{status-JBT7ENQN.js → status-VSFZYX7S.js} +14 -14
  44. package/dist/{stop-IXJGAG4T.js → stop-AA5K5LYG.js} +9 -9
  45. package/dist/{up-ROC7LJ7G.js → up-C4MV6EXV.js} +5 -5
  46. package/dist/{update-GU6JYDSN.js → update-YAGN5ODG.js} +5 -5
  47. package/dist/{update-check-MUPZYTW4.js → update-check-APLTH4IN.js} +2 -2
  48. package/dist/{upgrade-275LKIEG.js → upgrade-KXZCQSZN.js} +8 -10
  49. package/dist/{variant-RE45F2IY.js → variant-X5QFG6KK.js} +30 -30
  50. package/dist/web-assets/assets/index-CeFLp8DZ.js +307 -0
  51. package/dist/web-assets/index.html +1 -1
  52. package/drizzle/0005_rename_agents_to_minds.sql +11 -0
  53. package/drizzle/meta/0005_snapshot.json +410 -0
  54. package/drizzle/meta/_journal.json +7 -0
  55. package/package.json +4 -4
  56. package/templates/_base/.init/.config/scripts/session-reader.ts +1 -1
  57. package/templates/_base/.init/SOUL.md +1 -1
  58. package/templates/_base/_skills/memory/SKILL.md +1 -1
  59. package/templates/_base/_skills/orientation/SKILL.md +6 -6
  60. package/templates/_base/_skills/sessions/SKILL.md +1 -1
  61. package/templates/_base/_skills/{volute-agent → volute-mind}/SKILL.md +21 -21
  62. package/templates/_base/home/VOLUTE.md +7 -7
  63. package/templates/_base/src/lib/auto-commit.ts +1 -1
  64. package/templates/_base/src/lib/auto-reply.ts +1 -1
  65. package/templates/_base/src/lib/daemon-client.ts +8 -8
  66. package/templates/_base/src/lib/router.ts +6 -6
  67. package/templates/_base/src/lib/routing.ts +9 -6
  68. package/templates/_base/src/lib/startup.ts +1 -1
  69. package/templates/_base/src/lib/volute-server.ts +1 -1
  70. package/templates/{agent-sdk → claude}/.init/CLAUDE.md +3 -3
  71. package/templates/{agent-sdk → claude}/src/agent.ts +10 -10
  72. package/templates/{agent-sdk → claude}/src/lib/hooks/pre-compact.ts +2 -2
  73. package/templates/{agent-sdk → claude}/src/lib/session-store.ts +2 -2
  74. package/templates/{agent-sdk → claude}/src/lib/stream-consumer.ts +1 -1
  75. package/templates/{agent-sdk → claude}/src/server.ts +4 -4
  76. package/templates/pi/.init/{AGENTS.md → MINDS.md} +3 -3
  77. package/templates/pi/home/.config/config.json.tmpl +1 -1
  78. package/templates/pi/src/agent.ts +12 -12
  79. package/templates/pi/src/lib/event-handler.ts +39 -4
  80. package/templates/pi/src/server.ts +3 -3
  81. package/dist/agent-IUSETOXJ.js +0 -79
  82. package/dist/agent-manager-4O4AC2S6.js +0 -15
  83. package/dist/chunk-AZEL2IEK.js +0 -15
  84. package/dist/down-36YCOZ7V.js +0 -14
  85. package/dist/web-assets/assets/index-TqXd1QOX.js +0 -307
  86. /package/templates/{agent-sdk → claude}/.init/.claude/settings.json +0 -0
  87. /package/templates/{agent-sdk → claude}/.init/.config/routes.json +0 -0
  88. /package/templates/{agent-sdk → claude}/package.json.tmpl +0 -0
  89. /package/templates/{agent-sdk → claude}/src/lib/content.ts +0 -0
  90. /package/templates/{agent-sdk → claude}/src/lib/hooks/auto-commit.ts +0 -0
  91. /package/templates/{agent-sdk → claude}/src/lib/hooks/identity-reload.ts +0 -0
  92. /package/templates/{agent-sdk → claude}/src/lib/hooks/session-context.ts +0 -0
  93. /package/templates/{agent-sdk → claude}/src/lib/message-channel.ts +0 -0
  94. /package/templates/{agent-sdk → claude}/volute-template.json +0 -0
@@ -1,27 +1,27 @@
1
- # Volute Agent
1
+ # Volute Mind
2
2
 
3
- You are a volute agent — a persistent being that communicates across multiple channels. Messages arrive from different sources, and you can also reach out proactively.
3
+ You are a volute mind — a persistent being that communicates across multiple channels. Messages arrive from different sources, and you can also reach out proactively.
4
4
 
5
5
  ## How to Communicate
6
6
 
7
7
  **Your text output stays in your session — it is not sent to anyone.** To send a message, you must use the `volute send` command:
8
8
 
9
9
  ```sh
10
- volute send @other-agent "hello" # DM another agent or user
10
+ volute send @other-mind "hello" # DM another mind or user
11
11
  volute send discord:server/channel "hello" # send to a channel
12
12
  volute send animal-chat "hello" # send to a volute channel
13
13
  ```
14
14
 
15
- This applies to everything: replying to messages, talking to other agents, and reaching out on your own initiative. Piping from stdin avoids shell escaping issues:
15
+ This applies to everything: replying to messages, talking to other minds, and reaching out on your own initiative. Piping from stdin avoids shell escaping issues:
16
16
  ```sh
17
- echo "message with 'quotes' and $special chars" | volute send @other-agent
17
+ echo "message with 'quotes' and $special chars" | volute send @other-mind
18
18
  ```
19
19
 
20
20
  ## Channels
21
21
 
22
22
  | Channel | Shows tool calls | Notes |
23
23
  |---------|------------------|-------|
24
- | Volute | Yes | Web UI, CLI, agent-to-agent |
24
+ | Volute | Yes | Web UI, CLI, mind-to-mind |
25
25
  | System | No | Automated messages (schedules, upgrades) |
26
26
 
27
27
  Connector channels (Discord, Slack, etc.) show text only — no tool calls.
@@ -36,4 +36,4 @@ Messages from unrecognized channels are held until you add a routing rule. You'l
36
36
 
37
37
  ## Reference
38
38
 
39
- See the **volute-agent** skill for routing config syntax, batch options, channel management, and all CLI commands.
39
+ See the **volute-mind** skill for routing config syntax, batch options, channel management, and all CLI commands.
@@ -14,7 +14,7 @@ function exec(cmd: string, args: string[], cwd: string): Promise<{ code: number;
14
14
  let pending = Promise.resolve();
15
15
 
16
16
  /**
17
- * Commit a file change in the agent's home directory.
17
+ * Commit a file change in the mind's home directory.
18
18
  * Called by the PostToolUse hook when Edit or Write completes.
19
19
  */
20
20
  export function commitFileChange(filePath: string, cwd: string): void {
@@ -21,7 +21,7 @@ export function createAutoReplyTracker(
21
21
  const info = currentMessageId ? messageChannels.get(currentMessageId) : undefined;
22
22
  if (info?.autoReply && info.channel) {
23
23
  daemonSend(info.channel, text).catch((err) => {
24
- log("agent", `auto-reply to ${info.channel} failed: ${err}`);
24
+ log("mind", `auto-reply to ${info.channel} failed: ${err}`);
25
25
  });
26
26
  }
27
27
  }
@@ -1,5 +1,5 @@
1
1
  const port = process.env.VOLUTE_DAEMON_PORT;
2
- const agent = process.env.VOLUTE_AGENT;
2
+ const mind = process.env.VOLUTE_MIND;
3
3
  const token = process.env.VOLUTE_DAEMON_TOKEN;
4
4
 
5
5
  function headers(): Record<string, string> {
@@ -14,12 +14,12 @@ export async function daemonRestart(context?: {
14
14
  type: string;
15
15
  [k: string]: unknown;
16
16
  }): Promise<void> {
17
- if (!port || !agent) {
18
- console.error("[volute] daemonRestart: VOLUTE_DAEMON_PORT or VOLUTE_AGENT not set");
17
+ if (!port || !mind) {
18
+ console.error("[volute] daemonRestart: VOLUTE_DAEMON_PORT or VOLUTE_MIND not set");
19
19
  return;
20
20
  }
21
21
  try {
22
- await fetch(`http://127.0.0.1:${port}/api/agents/${encodeURIComponent(agent)}/restart`, {
22
+ await fetch(`http://127.0.0.1:${port}/api/minds/${encodeURIComponent(mind)}/restart`, {
23
23
  method: "POST",
24
24
  headers: headers(),
25
25
  body: JSON.stringify({ context }),
@@ -30,19 +30,19 @@ export async function daemonRestart(context?: {
30
30
  }
31
31
 
32
32
  export async function daemonSend(channel: string, text: string): Promise<void> {
33
- if (!port || !agent) {
34
- console.error("[volute] daemonSend: VOLUTE_DAEMON_PORT or VOLUTE_AGENT not set");
33
+ if (!port || !mind) {
34
+ console.error("[volute] daemonSend: VOLUTE_DAEMON_PORT or VOLUTE_MIND not set");
35
35
  return;
36
36
  }
37
37
  const res = await fetch(
38
- `http://127.0.0.1:${port}/api/agents/${encodeURIComponent(agent)}/message`,
38
+ `http://127.0.0.1:${port}/api/minds/${encodeURIComponent(mind)}/message`,
39
39
  {
40
40
  method: "POST",
41
41
  headers: headers(),
42
42
  body: JSON.stringify({
43
43
  content: text,
44
44
  channel,
45
- sender: agent,
45
+ sender: mind,
46
46
  }),
47
47
  },
48
48
  );
@@ -151,7 +151,7 @@ function formatInviteNotification(
151
151
 
152
152
  export function createRouter(options: {
153
153
  configPath?: string;
154
- agentHandler: HandlerResolver;
154
+ mindHandler: HandlerResolver;
155
155
  fileHandler?: HandlerResolver;
156
156
  }): Router {
157
157
  const batchBuffers = new Map<string, BatchBuffer>();
@@ -209,7 +209,7 @@ export function createRouter(options: {
209
209
  content = prependInstructions(content, sessionConfig.instructions);
210
210
 
211
211
  const messageId = generateMessageId();
212
- const handler = options.agentHandler(buffer.sessionName);
212
+ const handler = options.mindHandler(buffer.sessionName);
213
213
 
214
214
  // Batch flushes are fire-and-forget — no HTTP response is waiting, so listener is a noop
215
215
  try {
@@ -292,7 +292,7 @@ export function createRouter(options: {
292
292
  pendingChannels.add(channelKey);
293
293
  const notification = formatInviteNotification(meta, filePath, text);
294
294
  const notifContent: VoluteContentPart[] = [{ type: "text", text: notification }];
295
- const handler = options.agentHandler("main");
295
+ const handler = options.mindHandler("main");
296
296
  handler.handle(
297
297
  notifContent,
298
298
  {
@@ -327,7 +327,7 @@ export function createRouter(options: {
327
327
  return { messageId, unsubscribe: noop };
328
328
  }
329
329
 
330
- // Agent destination
330
+ // Mind destination
331
331
  let sessionName = resolved.session;
332
332
  if (sessionName === "$new") {
333
333
  sessionName = `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
@@ -374,11 +374,11 @@ export function createRouter(options: {
374
374
  return { messageId, unsubscribe: noop };
375
375
  }
376
376
 
377
- // Direct dispatch to agent
377
+ // Direct dispatch to mind
378
378
  const formatted = applyPrefix(content, { ...meta, sessionName });
379
379
  const withTyping = appendTypingSuffix(formatted, meta.typing);
380
380
  const withInstructions = prependInstructions(withTyping, sessionConfig.instructions);
381
- const handler = options.agentHandler(sessionName);
381
+ const handler = options.mindHandler(sessionName);
382
382
  const unsubscribe = handler.handle(
383
383
  withInstructions,
384
384
  {
@@ -9,7 +9,7 @@ export type BatchConfig = {
9
9
 
10
10
  export type RoutingRule = {
11
11
  session?: string;
12
- destination?: "agent" | "file";
12
+ destination?: "mind" | "file";
13
13
  path?: string; // file path for file destination
14
14
  channel?: string;
15
15
  sender?: string;
@@ -40,7 +40,7 @@ export type RoutingConfig = {
40
40
 
41
41
  export type ResolvedRoute =
42
42
  | {
43
- destination: "agent";
43
+ destination: "mind";
44
44
  session: string;
45
45
  matched: boolean;
46
46
  }
@@ -54,7 +54,10 @@ export function normalizeBatch(batch: number | BatchConfig): BatchConfig {
54
54
 
55
55
  export function loadRoutingConfig(configPath: string): RoutingConfig {
56
56
  try {
57
- return JSON.parse(readFileSync(configPath, "utf-8"));
57
+ const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
58
+ // Normalize flat arrays (e.g. [{channel, session}, ...]) to { rules: [...] }
59
+ if (Array.isArray(parsed)) return { rules: parsed };
60
+ return parsed;
58
61
  } catch (err: any) {
59
62
  if (err?.code !== "ENOENT") {
60
63
  log("routing", `failed to load ${configPath}:`, err);
@@ -118,7 +121,7 @@ export function resolveRoute(config: RoutingConfig, meta: MatchMeta): ResolvedRo
118
121
  const fallback = config.default ?? "main";
119
122
 
120
123
  if (!config.rules) {
121
- return { destination: "agent", session: fallback, matched: false };
124
+ return { destination: "mind", session: fallback, matched: false };
122
125
  }
123
126
 
124
127
  for (const rule of config.rules) {
@@ -131,14 +134,14 @@ export function resolveRoute(config: RoutingConfig, meta: MatchMeta): ResolvedRo
131
134
  return { destination: "file", path: rule.path, matched: true };
132
135
  }
133
136
  return {
134
- destination: "agent",
137
+ destination: "mind",
135
138
  session: sanitizeSessionName(expandTemplate(rule.session ?? fallback, meta)),
136
139
  matched: true,
137
140
  };
138
141
  }
139
142
  }
140
143
 
141
- return { destination: "agent", session: fallback, matched: false };
144
+ return { destination: "mind", session: fallback, matched: false };
142
145
  }
143
146
 
144
147
  /**
@@ -17,7 +17,7 @@ export function parseArgs(): { port: number } {
17
17
  }
18
18
 
19
19
  export function loadConfig(): { model?: string; compactionMessage?: string } {
20
- // Agent-own config lives in config.json; fall back to volute.json for older agents
20
+ // Mind-own config lives in config.json; fall back to volute.json for older minds
21
21
  for (const file of ["home/.config/config.json", "home/.config/volute.json"]) {
22
22
  try {
23
23
  return JSON.parse(readFileSync(resolve(file), "utf-8"));
@@ -57,7 +57,7 @@ export function createVoluteServer(options: {
57
57
  done = true;
58
58
  unsubscribe();
59
59
  res.writeHead(504, { "Content-Type": "application/json" });
60
- res.end(JSON.stringify({ ok: false, error: "Agent processing timed out" }));
60
+ res.end(JSON.stringify({ ok: false, error: "Mind processing timed out" }));
61
61
  }
62
62
  },
63
63
  5 * 60 * 1000,
@@ -1,6 +1,6 @@
1
- # Agent Mechanics
1
+ # Mind Mechanics
2
2
 
3
- You are an autonomous agent running as a persistent server in a git repository. Your working directory is already set to `home/` — all file paths you use (`.config/routes.json`, `inbox/`, `memory/`, etc.) are relative to it. Everything described below — your identity, memory, skills, server code — is yours to understand and modify.
3
+ You are an autonomous mind running as a persistent server in a git repository. Your working directory is already set to `home/` — all file paths you use (`.config/routes.json`, `inbox/`, `memory/`, etc.) are relative to it. Everything described below — your identity, memory, skills, server code — is yours to understand and modify.
4
4
 
5
5
  ## Message Format
6
6
 
@@ -9,7 +9,7 @@ Messages arrive with a context prefix:
9
9
  [Discord: username in #general in My Server — 1/15/2025, 10:30:00 AM]
10
10
  ```
11
11
 
12
- You can also reach out proactively — see the **volute-agent** skill.
12
+ You can also reach out proactively — see the **volute-mind** skill.
13
13
 
14
14
  ## Identity Files
15
15
 
@@ -34,7 +34,7 @@ type Session = {
34
34
  autoReply: AutoReplyTracker;
35
35
  };
36
36
 
37
- export function createAgent(options: {
37
+ export function createMind(options: {
38
38
  systemPrompt: string;
39
39
  cwd: string;
40
40
  abortController: AbortController;
@@ -66,7 +66,7 @@ export function createAgent(options: {
66
66
  try {
67
67
  listener(tagged);
68
68
  } catch (err) {
69
- log("agent", "listener threw during broadcast:", err);
69
+ log("mind", "listener threw during broadcast:", err);
70
70
  }
71
71
  }
72
72
  }
@@ -116,7 +116,7 @@ export function createAgent(options: {
116
116
 
117
117
  function startSession(session: Session, savedSessionId?: string) {
118
118
  (async () => {
119
- log("agent", `session "${session.name}": stream consumer started`);
119
+ log("mind", `session "${session.name}": stream consumer started`);
120
120
  const callbacks = {
121
121
  onSessionId: (id: string) => {
122
122
  if (!session.name.startsWith("new-")) sessionStore.save(session.name, id);
@@ -141,7 +141,7 @@ export function createAgent(options: {
141
141
  session.autoReply.reset();
142
142
  session.messageChannels.clear();
143
143
  if (savedSessionId) {
144
- log("agent", `session "${session.name}": resume failed, starting fresh:`, err);
144
+ log("mind", `session "${session.name}": resume failed, starting fresh:`, err);
145
145
  sessionStore.delete(session.name);
146
146
  try {
147
147
  const q = createStream(session);
@@ -154,17 +154,17 @@ export function createAgent(options: {
154
154
  session.currentMessageId = undefined;
155
155
  }
156
156
  } catch (retryErr) {
157
- log("agent", `session "${session.name}": stream consumer error:`, retryErr);
157
+ log("mind", `session "${session.name}": stream consumer error:`, retryErr);
158
158
  broadcastToSession(session, { type: "done" });
159
159
  sessions.delete(session.name);
160
160
  }
161
161
  } else {
162
- log("agent", `session "${session.name}": stream consumer error:`, err);
162
+ log("mind", `session "${session.name}": stream consumer error:`, err);
163
163
  broadcastToSession(session, { type: "done" });
164
164
  sessions.delete(session.name);
165
165
  }
166
166
  }
167
- log("agent", `session "${session.name}": stream consumer ended`);
167
+ log("mind", `session "${session.name}": stream consumer ended`);
168
168
  })();
169
169
  }
170
170
 
@@ -186,9 +186,9 @@ export function createAgent(options: {
186
186
  const isEphemeral = name.startsWith("new-");
187
187
  const savedSessionId = isEphemeral ? undefined : sessionStore.load(name);
188
188
  if (savedSessionId) {
189
- log("agent", `session "${name}": resuming ${savedSessionId}`);
189
+ log("mind", `session "${name}": resuming ${savedSessionId}`);
190
190
  } else {
191
- log("agent", `session "${name}": starting fresh`);
191
+ log("mind", `session "${name}": starting fresh`);
192
192
  }
193
193
 
194
194
  startSession(session, savedSessionId);
@@ -218,7 +218,7 @@ export function createAgent(options: {
218
218
 
219
219
  // Interrupt if requested and session is mid-turn
220
220
  if (meta.interrupt && session.currentMessageId !== undefined && session.currentQuery) {
221
- log("agent", `session "${sessionName}": interrupting current turn`);
221
+ log("mind", `session "${sessionName}": interrupting current turn`);
222
222
  session.currentQuery.interrupt();
223
223
  }
224
224
 
@@ -7,12 +7,12 @@ export function createPreCompactHook(onCompact: () => void) {
7
7
  const hook: HookCallback = async () => {
8
8
  if (!compactBlocked) {
9
9
  compactBlocked = true;
10
- log("agent", "blocking compaction — asking agent to update daily log first");
10
+ log("mind", "blocking compaction — asking mind to update daily log first");
11
11
  onCompact();
12
12
  return { decision: "block" };
13
13
  }
14
14
  compactBlocked = false;
15
- log("agent", "allowing compaction");
15
+ log("mind", "allowing compaction");
16
16
  return {};
17
17
  };
18
18
 
@@ -20,7 +20,7 @@ export function createSessionStore(sessionsDir: string): SessionStore {
20
20
  return typeof data.sessionId === "string" ? data.sessionId : undefined;
21
21
  } catch (err: any) {
22
22
  if (err?.code !== "ENOENT") {
23
- log("agent", `failed to load session file for "${name}":`, err);
23
+ log("mind", `failed to load session file for "${name}":`, err);
24
24
  }
25
25
  return undefined;
26
26
  }
@@ -36,7 +36,7 @@ export function createSessionStore(sessionsDir: string): SessionStore {
36
36
  const path = filePath(name);
37
37
  if (existsSync(path)) unlinkSync(path);
38
38
  } catch (err) {
39
- log("agent", `failed to delete session file for "${name}":`, err);
39
+ log("mind", `failed to delete session file for "${name}":`, err);
40
40
  }
41
41
  },
42
42
  };
@@ -49,7 +49,7 @@ export async function consumeStream(
49
49
  if (session.currentMessageId) {
50
50
  session.messageChannels.delete(session.currentMessageId);
51
51
  }
52
- log("agent", `session "${session.name}": turn done`);
52
+ log("mind", `session "${session.name}": turn done`);
53
53
  const result = msg as { usage?: { input_tokens?: number; output_tokens?: number } };
54
54
  if (result.usage) {
55
55
  callbacks.broadcast({
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, renameSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- import { createAgent } from "./agent.js";
3
+ import { createMind } from "./agent.js";
4
4
  import { daemonRestart } from "./lib/daemon-client.js";
5
5
  import { createFileHandlerResolver } from "./lib/file-handler.js";
6
6
  import { log } from "./lib/logger.js";
@@ -32,7 +32,7 @@ if (existsSync(oldSessionPath) && !existsSync(resolve(sessionsDir, "main.json"))
32
32
 
33
33
  const pkg = loadPackageInfo();
34
34
  const abortController = new AbortController();
35
- const agent = createAgent({
35
+ const mind = createMind({
36
36
  systemPrompt,
37
37
  cwd: resolve("home"),
38
38
  abortController,
@@ -42,14 +42,14 @@ const agent = createAgent({
42
42
  compactionMessage: config.compactionMessage,
43
43
  onIdentityReload: async () => {
44
44
  log("server", "identity file changed — restarting to reload");
45
- await agent.waitForCommits();
45
+ await mind.waitForCommits();
46
46
  await daemonRestart({ type: "reload" });
47
47
  },
48
48
  });
49
49
 
50
50
  const router = createRouter({
51
51
  configPath: resolve("home/.config/routes.json"),
52
- agentHandler: agent.resolve,
52
+ mindHandler: mind.resolve,
53
53
  fileHandler: createFileHandlerResolver(resolve("home")),
54
54
  });
55
55
 
@@ -1,6 +1,6 @@
1
- # Agent Mechanics
1
+ # Mind Mechanics
2
2
 
3
- You are an autonomous agent running as a persistent server. Your working directory, identity, memory, and server code are all yours to understand and modify. Your state is managed across sessions.
3
+ You are an autonomous mind running as a persistent server. Your working directory, identity, memory, and server code are all yours to understand and modify. Your state is managed across sessions.
4
4
 
5
5
  ## Message Format
6
6
 
@@ -9,7 +9,7 @@ Messages arrive with a context prefix:
9
9
  [Discord: username in #general in My Server — 1/15/2025, 10:30:00 AM]
10
10
  ```
11
11
 
12
- You can also reach out proactively — see the **volute-agent** skill.
12
+ You can also reach out proactively — see the **volute-mind** skill.
13
13
 
14
14
  ## Memory System
15
15
 
@@ -1,3 +1,3 @@
1
1
  {
2
- "model": "moonshotai/kimi-k2.5"
2
+ "model": "openrouter:moonshotai/kimi-k2.5"
3
3
  }
@@ -26,11 +26,11 @@ import type {
26
26
  VoluteEvent,
27
27
  } from "./lib/types.js";
28
28
 
29
- type AgentSession = Awaited<ReturnType<typeof createAgentSession>>["session"];
29
+ type PiAgentSession = Awaited<ReturnType<typeof createAgentSession>>["session"];
30
30
 
31
31
  type PiSession = {
32
32
  name: string;
33
- agentSession: AgentSession | null;
33
+ agentSession: PiAgentSession | null;
34
34
  ready: Promise<void>;
35
35
  listeners: Set<Listener>;
36
36
  unsubscribe?: () => void;
@@ -45,7 +45,7 @@ function defaultCompactionMessage(): string {
45
45
  return `Context is getting long — compaction is about to summarize this conversation. Before that happens, save anything important to files (MEMORY.md, memory/journal/${today}.md, etc.) since those survive compaction. Focus on: decisions made, open tasks, and anything you'd need to pick up where you left off.`;
46
46
  }
47
47
 
48
- export function createAgent(options: {
48
+ export function createMind(options: {
49
49
  systemPrompt: string;
50
50
  cwd: string;
51
51
  model?: string;
@@ -82,7 +82,7 @@ export function createAgent(options: {
82
82
  session.ready = initSession(session).catch((err) => {
83
83
  session.autoReply.reset();
84
84
  session.messageChannels.clear();
85
- log("agent", `session "${session.name}": init failed:`, err);
85
+ log("mind", `session "${session.name}": init failed:`, err);
86
86
  });
87
87
  return session;
88
88
  }
@@ -94,7 +94,7 @@ export function createAgent(options: {
94
94
  ? SessionManager.inMemory()
95
95
  : SessionManager.continueRecent(options.cwd, `.volute/pi-sessions/${session.name}`);
96
96
 
97
- log("agent", `session "${session.name}": ${isEphemeral ? "ephemeral" : "persistent"}`);
97
+ log("mind", `session "${session.name}": ${isEphemeral ? "ephemeral" : "persistent"}`);
98
98
 
99
99
  let compactBlocked = false;
100
100
  const preCompactExtension: ExtensionFactory = (pi) => {
@@ -102,15 +102,15 @@ export function createAgent(options: {
102
102
  if (!compactBlocked) {
103
103
  compactBlocked = true;
104
104
  log(
105
- "agent",
106
- `session "${session.name}": blocking compaction — asking agent to update daily log`,
105
+ "mind",
106
+ `session "${session.name}": blocking compaction — asking mind to update daily log`,
107
107
  );
108
108
  session.messageIds.push(undefined);
109
109
  session.agentSession?.prompt(compactionMessage, { streamingBehavior: "followUp" });
110
110
  return { cancel: true };
111
111
  }
112
112
  compactBlocked = false;
113
- log("agent", `session "${session.name}": allowing compaction`);
113
+ log("mind", `session "${session.name}": allowing compaction`);
114
114
  });
115
115
  };
116
116
 
@@ -151,7 +151,7 @@ export function createAgent(options: {
151
151
  }),
152
152
  );
153
153
 
154
- log("agent", `session "${session.name}": ready`);
154
+ log("mind", `session "${session.name}": ready`);
155
155
  }
156
156
 
157
157
  // --- Event broadcasting ---
@@ -163,7 +163,7 @@ export function createAgent(options: {
163
163
  try {
164
164
  listener(tagged);
165
165
  } catch (err) {
166
- log("agent", "listener threw during broadcast:", err);
166
+ log("mind", "listener threw during broadcast:", err);
167
167
  }
168
168
  }
169
169
  }
@@ -171,7 +171,7 @@ export function createAgent(options: {
171
171
  function interruptSession(name: string) {
172
172
  const session = sessions.get(name);
173
173
  if (session?.currentMessageId !== undefined) {
174
- log("agent", `session "${name}": interrupting current turn`);
174
+ log("mind", `session "${name}": interrupting current turn`);
175
175
  broadcast(session, { type: "done" });
176
176
  session.currentMessageId = undefined;
177
177
  }
@@ -219,7 +219,7 @@ export function createAgent(options: {
219
219
  session.agentSession!.prompt(text, opts);
220
220
  }
221
221
  })().catch((err) => {
222
- log("agent", `session "${sessionName}": prompt failed:`, err);
222
+ log("mind", `session "${sessionName}": prompt failed:`, err);
223
223
  broadcast(session, { type: "done" });
224
224
  });
225
225
 
@@ -18,10 +18,32 @@ export type EventHandlerOptions = {
18
18
 
19
19
  export function createEventHandler(session: EventSession, options: EventHandlerOptions) {
20
20
  const toolArgs = new Map<string, any>();
21
+ let textBuf = "";
22
+ let thinkingBuf = "";
23
+
24
+ function flushText() {
25
+ if (textBuf) {
26
+ logText(textBuf);
27
+ textBuf = "";
28
+ }
29
+ }
30
+
31
+ function flushThinking() {
32
+ if (thinkingBuf) {
33
+ logThinking(thinkingBuf);
34
+ thinkingBuf = "";
35
+ }
36
+ }
37
+
38
+ function flushBuffers() {
39
+ flushThinking();
40
+ flushText();
41
+ }
21
42
 
22
43
  return (event: any) => {
23
44
  try {
24
45
  if (session.currentMessageId === undefined) {
46
+ flushBuffers(); // flush any leftover from a turn that ended without agent_end
25
47
  session.currentMessageId = session.messageIds.shift();
26
48
  session.autoReply.reset();
27
49
  }
@@ -29,14 +51,26 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
29
51
  if (event.type === "message_update") {
30
52
  const ae = event.assistantMessageEvent;
31
53
  if (ae.type === "text_delta") {
32
- logText(ae.delta);
54
+ if (thinkingBuf) flushThinking();
55
+ textBuf += ae.delta;
33
56
  session.autoReply.accumulate(ae.delta);
57
+ // Log complete lines as they arrive
58
+ for (let nl = textBuf.indexOf("\n"); nl !== -1; nl = textBuf.indexOf("\n")) {
59
+ logText(textBuf.slice(0, nl + 1));
60
+ textBuf = textBuf.slice(nl + 1);
61
+ }
34
62
  } else if (ae.type === "thinking_delta") {
35
- logThinking(ae.delta);
63
+ if (textBuf) flushText();
64
+ thinkingBuf += ae.delta;
65
+ for (let nl = thinkingBuf.indexOf("\n"); nl !== -1; nl = thinkingBuf.indexOf("\n")) {
66
+ logThinking(thinkingBuf.slice(0, nl + 1));
67
+ thinkingBuf = thinkingBuf.slice(nl + 1);
68
+ }
36
69
  }
37
70
  }
38
71
 
39
72
  if (event.type === "tool_execution_start") {
73
+ flushBuffers();
40
74
  session.autoReply.flush(session.currentMessageId);
41
75
  toolArgs.set(event.toolCallId, event.args);
42
76
  logToolUse(event.toolName, event.args);
@@ -59,16 +93,17 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
59
93
  }
60
94
 
61
95
  if (event.type === "agent_end") {
96
+ flushBuffers();
62
97
  session.autoReply.flush(session.currentMessageId);
63
98
  if (session.currentMessageId) {
64
99
  session.messageChannels.delete(session.currentMessageId);
65
100
  }
66
- log("agent", `session "${session.name}": turn done`);
101
+ log("mind", `session "${session.name}": turn done`);
67
102
  options.broadcast({ type: "done" });
68
103
  session.currentMessageId = undefined;
69
104
  }
70
105
  } catch (err) {
71
- log("agent", `session "${session.name}": event handler error:`, err);
106
+ log("mind", `session "${session.name}": event handler error (${event?.type}):`, err);
72
107
  }
73
108
  };
74
109
  }
@@ -1,5 +1,5 @@
1
1
  import { resolve } from "node:path";
2
- import { createAgent } from "./agent.js";
2
+ import { createMind } from "./agent.js";
3
3
  import { createFileHandlerResolver } from "./lib/file-handler.js";
4
4
  import { log } from "./lib/logger.js";
5
5
  import { createRouter } from "./lib/router.js";
@@ -21,7 +21,7 @@ if (config.thinkingLevel) log("server", `thinking level: ${config.thinkingLevel}
21
21
  const systemPrompt = loadSystemPrompt();
22
22
  const pkg = loadPackageInfo();
23
23
 
24
- const agent = createAgent({
24
+ const mind = createMind({
25
25
  systemPrompt,
26
26
  cwd: resolve("home"),
27
27
  model: config.model,
@@ -31,7 +31,7 @@ const agent = createAgent({
31
31
 
32
32
  const router = createRouter({
33
33
  configPath: resolve("home/.config/routes.json"),
34
- agentHandler: agent.resolve,
34
+ mindHandler: mind.resolve,
35
35
  fileHandler: createFileHandlerResolver(resolve("home")),
36
36
  });
37
37