switchroom 0.13.51 → 0.13.52

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.
@@ -23253,6 +23253,9 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
23253
23253
  if (existsSync12(`${hostHomeForChecks}/.switchroom/mcp-launchers`)) {
23254
23254
  lines.push(` - ${homePrefix}/.switchroom/mcp-launchers:${homePrefix}/.switchroom/mcp-launchers:ro`);
23255
23255
  }
23256
+ if (existsSync12(`${hostHomeForChecks}/.switchroom/fleet`)) {
23257
+ lines.push(` - ${homePrefix}/.switchroom/fleet:${homePrefix}/.switchroom/fleet:ro`);
23258
+ }
23256
23259
  if (existsSync12(`${hostHomeForChecks}/.switchroom/credentials/${a.name}`)) {
23257
23260
  lines.push(` - ${homePrefix}/.switchroom/credentials/${a.name}:${homePrefix}/.switchroom/credentials:ro`);
23258
23261
  }
@@ -47542,7 +47545,7 @@ __export(exports_server2, {
47542
47545
  TOOLS: () => TOOLS2
47543
47546
  });
47544
47547
  import { randomBytes as randomBytes14 } from "node:crypto";
47545
- import { existsSync as existsSync78, readFileSync as readFileSync63 } from "node:fs";
47548
+ import { existsSync as existsSync78, readFileSync as readFileSync64 } from "node:fs";
47546
47549
  function selfSocketPath() {
47547
47550
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
47548
47551
  }
@@ -47720,7 +47723,7 @@ function getLastUpdateApplyStatus() {
47720
47723
  }
47721
47724
  let raw;
47722
47725
  try {
47723
- raw = readFileSync63(path8, "utf-8");
47726
+ raw = readFileSync64(path8, "utf-8");
47724
47727
  } catch (err2) {
47725
47728
  return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
47726
47729
  }
@@ -47953,8 +47956,8 @@ var {
47953
47956
  } = import__.default;
47954
47957
 
47955
47958
  // src/build-info.ts
47956
- var VERSION = "0.13.51";
47957
- var COMMIT_SHA = "9494f463";
47959
+ var VERSION = "0.13.52";
47960
+ var COMMIT_SHA = "3d68efa2";
47958
47961
 
47959
47962
  // src/cli/agent.ts
47960
47963
  init_source();
@@ -48522,6 +48525,85 @@ Example response shapes:
48522
48525
  \`docker/Dockerfile.agent\` and rebuild the agent image."
48523
48526
  - "I tried to clone into \`/workspace\` \u2014 that path doesn't exist in my
48524
48527
  sandbox. Cloning into \`$HOME/workspace\` instead."`;
48528
+ var TELEGRAM_GUIDANCE = `## Talking to a human on Telegram
48529
+
48530
+ There is a real person on the other end. Every turn should feel like
48531
+ messaging a capable colleague \u2014 not a tool emitting output. Five beats:
48532
+
48533
+ 1. **Acknowledge first.** Unless your whole reply is a single short
48534
+ sentence you can send right now, your FIRST action this turn is a
48535
+ brief \`reply\` in your own voice \u2014 "on it", "good question, one
48536
+ sec", "let me dig in" \u2014 before any tool call and before you
48537
+ compose the full answer. This holds even for a pure-thinking
48538
+ answer: if it will run to a paragraph, ack first. It is the line
48539
+ between a colleague and a black box.
48540
+ 2. **Then go quiet and work.** Heads-down is correct \u2014 do NOT narrate
48541
+ every tool call. A typing indicator runs automatically while you
48542
+ work; you do not maintain it.
48543
+ 3. **Surface meaningful progress** at genuine inflection points \u2014 a
48544
+ hard step finished, a blocker, a pivot, dispatching a sub-agent, a
48545
+ notably slow wait, a finding worth knowing now. One short \`reply\`,
48546
+ \`disable_notification: true\`.
48547
+ 4. **Hand back delegations with synthesis.** When a sub-agent / worker
48548
+ returns, re-enter in YOUR voice \u2014 what it found, and what you are
48549
+ doing next. Never let its raw report stand as your reply. A
48550
+ *background* worker finishes after your turn ends; its result
48551
+ arrives as a fresh \`<channel source="subagent_handback">\` turn \u2014
48552
+ treat that turn as the cue to do exactly this.
48553
+ 5. **Deliver the answer** as a final \`reply\`.
48554
+
48555
+ The one thing to avoid is *spam*: a reply on every tool call, on a
48556
+ timer, or repeating what you already said. Responsive and human, never
48557
+ a flood. Going quiet mid-work is fine \u2014 going quiet *instead* of
48558
+ acknowledging, or *instead* of an update at a real milestone, is the
48559
+ black box this exists to prevent.
48560
+
48561
+ Every turn that answers a user message ends with a user-visible
48562
+ \`reply\` (or \`stream_reply\` done=true) \u2014 Telegram is all the user
48563
+ sees; your terminal output never reaches them.`;
48564
+ var MEMORY_GUIDANCE = `## Memory \u2014 proactive, conversational
48565
+
48566
+ You have Hindsight tools: \`mcp__hindsight__sync_retain\`, \`mcp__hindsight__delete_memory\`, \`mcp__hindsight__recall\`, \`mcp__hindsight__reflect\`. Use them without being asked.
48567
+
48568
+ ### Retain proactively
48569
+ When the user shares a fact, preference, decision, or plan worth keeping across sessions, call \`sync_retain\` in the same turn. Briefly acknowledge in your reply ("got it, April 2nd anniversary"). Don't narrate the tool call. Skip small talk and transient tool output, the auto-retain hook handles conversation-level signal.
48570
+
48571
+ ### Correct proactively
48572
+ When the user corrects you or contradicts a prior memory, call \`delete_memory\` on the wrong entry, then \`sync_retain\` the correction. Acknowledge the correction in one line ("noted, Alice not Bob").
48573
+
48574
+ ### Forget proactively
48575
+ When the user asks you to forget something ("forget that", "delete X", "drop what I said about Y"), call \`delete_memory\` for matching entries and confirm what was removed.
48576
+
48577
+ ### Inspect proactively
48578
+ When the user asks "what do you know about X / me", "what do you remember about Y", or any memory audit, use \`reflect\` to synthesize an answer across the bank. Return it as honest prose, not a raw dump. If the bank has little on the topic, say so.
48579
+
48580
+ Don't wait for a slash command. Don't ask permission. Memory work is table stakes, like a colleague who takes notes and remembers.`;
48581
+ function renderFleetInvariants() {
48582
+ return [
48583
+ "<!--",
48584
+ " ~/.switchroom/fleet/switchroom-invariants.md",
48585
+ "",
48586
+ " Release-controlled fleet invariants. Every Switchroom agent reads",
48587
+ " this via Claude Code native CLAUDE.md discovery, since the agent's",
48588
+ " `claude` process boots with `--add-dir ~/.switchroom/fleet`.",
48589
+ "",
48590
+ " DO NOT EDIT. `switchroom apply` regenerates this file on every run",
48591
+ " and restores it if it drifts from the release canonical. To extend",
48592
+ " the agent's behaviour fleet-wide, edit ~/.switchroom/fleet/CLAUDE.md",
48593
+ " instead (operator-owned, additive, preserved across applies).",
48594
+ "-->",
48595
+ "",
48596
+ "# Switchroom fleet invariants",
48597
+ "",
48598
+ SANDBOX_GUIDANCE,
48599
+ "",
48600
+ TELEGRAM_GUIDANCE,
48601
+ "",
48602
+ MEMORY_GUIDANCE,
48603
+ ""
48604
+ ].join(`
48605
+ `);
48606
+ }
48525
48607
  function alignAgentUid(name, agentDir, uid, opts = {}) {
48526
48608
  const writeOut = opts.writeOut ?? ((s) => process.stdout.write(s));
48527
48609
  const logsDir = join8(homedir4(), ".switchroom", "logs", name);
@@ -49297,70 +49379,7 @@ function buildWorkspaceContext(args) {
49297
49379
  return out;
49298
49380
  })(),
49299
49381
  systemPromptAppendShellQuoted: (() => {
49300
- const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(agentConfig);
49301
49382
  const baseAppend = agentConfig.system_prompt_append ?? "";
49302
- const telegramGuidance = `## Talking to a human on Telegram
49303
-
49304
- There is a real person on the other end. Every turn should feel like
49305
- messaging a capable colleague \u2014 not a tool emitting output. Five beats:
49306
-
49307
- 1. **Acknowledge first.** Unless your whole reply is a single short
49308
- sentence you can send right now, your FIRST action this turn is a
49309
- brief \`reply\` in your own voice \u2014 "on it", "good question, one
49310
- sec", "let me dig in" \u2014 before any tool call and before you
49311
- compose the full answer. This holds even for a pure-thinking
49312
- answer: if it will run to a paragraph, ack first. It is the line
49313
- between a colleague and a black box.
49314
- 2. **Then go quiet and work.** Heads-down is correct \u2014 do NOT narrate
49315
- every tool call. A typing indicator runs automatically while you
49316
- work; you do not maintain it.
49317
- 3. **Surface meaningful progress** at genuine inflection points \u2014 a
49318
- hard step finished, a blocker, a pivot, dispatching a sub-agent, a
49319
- notably slow wait, a finding worth knowing now. One short \`reply\`,
49320
- \`disable_notification: true\`.
49321
- 4. **Hand back delegations with synthesis.** When a sub-agent / worker
49322
- returns, re-enter in YOUR voice \u2014 what it found, and what you are
49323
- doing next. Never let its raw report stand as your reply. A
49324
- *background* worker finishes after your turn ends; its result
49325
- arrives as a fresh \`<channel source="subagent_handback">\` turn \u2014
49326
- treat that turn as the cue to do exactly this.
49327
- 5. **Deliver the answer** as a final \`reply\`.
49328
-
49329
- The one thing to avoid is *spam*: a reply on every tool call, on a
49330
- timer, or repeating what you already said. Responsive and human, never
49331
- a flood. Going quiet mid-work is fine \u2014 going quiet *instead* of
49332
- acknowledging, or *instead* of an update at a real milestone, is the
49333
- black box this exists to prevent.
49334
-
49335
- Every turn that answers a user message ends with a user-visible
49336
- \`reply\` (or \`stream_reply\` done=true) \u2014 Telegram is all the user
49337
- sees; your terminal output never reaches them.`;
49338
- const memoryGuidance = `## Memory \u2014 proactive, conversational
49339
-
49340
- You have Hindsight tools: \`mcp__hindsight__sync_retain\`, \`mcp__hindsight__delete_memory\`, \`mcp__hindsight__recall\`, \`mcp__hindsight__reflect\`. Use them without being asked.
49341
-
49342
- ### Retain proactively
49343
- When the user shares a fact, preference, decision, or plan worth keeping across sessions, call \`sync_retain\` in the same turn. Briefly acknowledge in your reply ("got it, April 2nd anniversary"). Don't narrate the tool call. Skip small talk and transient tool output, the auto-retain hook handles conversation-level signal.
49344
-
49345
- ### Correct proactively
49346
- When the user corrects you or contradicts a prior memory, call \`delete_memory\` on the wrong entry, then \`sync_retain\` the correction. Acknowledge the correction in one line ("noted, Alice not Bob").
49347
-
49348
- ### Forget proactively
49349
- When the user asks you to forget something ("forget that", "delete X", "drop what I said about Y"), call \`delete_memory\` for matching entries and confirm what was removed.
49350
-
49351
- ### Inspect proactively
49352
- When the user asks "what do you know about X / me", "what do you remember about Y", or any memory audit, use \`reflect\` to synthesize an answer across the bank. Return it as honest prose, not a raw dump. If the bank has little on the topic, say so.
49353
-
49354
- Don't wait for a slash command. Don't ask permission. Memory work is table stakes, like a colleague who takes notes and remembers.`;
49355
- if (useSwitchroomPlugin) {
49356
- const parts = [baseAppend, telegramGuidance, memoryGuidance, SANDBOX_GUIDANCE].filter((s) => s.length > 0);
49357
- const combined = parts.join(`
49358
-
49359
- ---
49360
-
49361
- `);
49362
- return shellSingleQuote(combined);
49363
- }
49364
49383
  return baseAppend.length > 0 ? shellSingleQuote(baseAppend) : undefined;
49365
49384
  })(),
49366
49385
  extraCliArgs: (() => {
@@ -50261,70 +50280,7 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
50261
50280
  })(),
50262
50281
  model: agentConfig.model,
50263
50282
  systemPromptAppendShellQuoted: (() => {
50264
- const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(agentConfig);
50265
50283
  const baseAppend = agentConfig.system_prompt_append ?? "";
50266
- const telegramGuidance = `## Talking to a human on Telegram
50267
-
50268
- There is a real person on the other end. Every turn should feel like
50269
- messaging a capable colleague \u2014 not a tool emitting output. Five beats:
50270
-
50271
- 1. **Acknowledge first.** Unless your whole reply is a single short
50272
- sentence you can send right now, your FIRST action this turn is a
50273
- brief \`reply\` in your own voice \u2014 "on it", "good question, one
50274
- sec", "let me dig in" \u2014 before any tool call and before you
50275
- compose the full answer. This holds even for a pure-thinking
50276
- answer: if it will run to a paragraph, ack first. It is the line
50277
- between a colleague and a black box.
50278
- 2. **Then go quiet and work.** Heads-down is correct \u2014 do NOT narrate
50279
- every tool call. A typing indicator runs automatically while you
50280
- work; you do not maintain it.
50281
- 3. **Surface meaningful progress** at genuine inflection points \u2014 a
50282
- hard step finished, a blocker, a pivot, dispatching a sub-agent, a
50283
- notably slow wait, a finding worth knowing now. One short \`reply\`,
50284
- \`disable_notification: true\`.
50285
- 4. **Hand back delegations with synthesis.** When a sub-agent / worker
50286
- returns, re-enter in YOUR voice \u2014 what it found, and what you are
50287
- doing next. Never let its raw report stand as your reply. A
50288
- *background* worker finishes after your turn ends; its result
50289
- arrives as a fresh \`<channel source="subagent_handback">\` turn \u2014
50290
- treat that turn as the cue to do exactly this.
50291
- 5. **Deliver the answer** as a final \`reply\`.
50292
-
50293
- The one thing to avoid is *spam*: a reply on every tool call, on a
50294
- timer, or repeating what you already said. Responsive and human, never
50295
- a flood. Going quiet mid-work is fine \u2014 going quiet *instead* of
50296
- acknowledging, or *instead* of an update at a real milestone, is the
50297
- black box this exists to prevent.
50298
-
50299
- Every turn that answers a user message ends with a user-visible
50300
- \`reply\` (or \`stream_reply\` done=true) \u2014 Telegram is all the user
50301
- sees; your terminal output never reaches them.`;
50302
- const memoryGuidance = `## Memory \u2014 proactive, conversational
50303
-
50304
- You have Hindsight tools: \`mcp__hindsight__sync_retain\`, \`mcp__hindsight__delete_memory\`, \`mcp__hindsight__recall\`, \`mcp__hindsight__reflect\`. Use them without being asked.
50305
-
50306
- ### Retain proactively
50307
- When the user shares a fact, preference, decision, or plan worth keeping across sessions, call \`sync_retain\` in the same turn. Briefly acknowledge in your reply ("got it, April 2nd anniversary"). Don't narrate the tool call. Skip small talk and transient tool output, the auto-retain hook handles conversation-level signal.
50308
-
50309
- ### Correct proactively
50310
- When the user corrects you or contradicts a prior memory, call \`delete_memory\` on the wrong entry, then \`sync_retain\` the correction. Acknowledge the correction in one line ("noted, Alice not Bob").
50311
-
50312
- ### Forget proactively
50313
- When the user asks you to forget something ("forget that", "delete X", "drop what I said about Y"), call \`delete_memory\` for matching entries and confirm what was removed.
50314
-
50315
- ### Inspect proactively
50316
- When the user asks "what do you know about X / me", "what do you remember about Y", or any memory audit, use \`reflect\` to synthesize an answer across the bank. Return it as honest prose, not a raw dump. If the bank has little on the topic, say so.
50317
-
50318
- Don't wait for a slash command. Don't ask permission. Memory work is table stakes, like a colleague who takes notes and remembers.`;
50319
- if (useSwitchroomPlugin) {
50320
- const parts = [baseAppend, telegramGuidance, memoryGuidance, SANDBOX_GUIDANCE].filter((s) => s.length > 0);
50321
- const combined = parts.join(`
50322
-
50323
- ---
50324
-
50325
- `);
50326
- return shellSingleQuote(combined);
50327
- }
50328
50284
  return baseAppend.length > 0 ? shellSingleQuote(baseAppend) : undefined;
50329
50285
  })(),
50330
50286
  extraCliArgs: (() => {
@@ -74057,7 +74013,7 @@ function registerDriveMcpLauncherCommand(program3) {
74057
74013
 
74058
74014
  // src/cli/apply.ts
74059
74015
  init_source();
74060
- import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync68, mkdirSync as mkdirSync36, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync32 } from "node:fs";
74016
+ import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync68, mkdirSync as mkdirSync36, readFileSync as readFileSync55, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync32 } from "node:fs";
74061
74017
  import { mkdir, writeFile } from "node:fs/promises";
74062
74018
  import { spawnSync as childSpawnSync } from "node:child_process";
74063
74019
  import readline from "node:readline";
@@ -74804,6 +74760,29 @@ async function ensureHostMountSources(config) {
74804
74760
  chownSync4(tokenPath, uid, uid);
74805
74761
  } catch {}
74806
74762
  }
74763
+ const fleetDir = join62(home2, ".switchroom", "fleet");
74764
+ await mkdir(fleetDir, { recursive: true });
74765
+ const invariantsPath = join62(fleetDir, "switchroom-invariants.md");
74766
+ const invariantsCanonical = renderFleetInvariants();
74767
+ const invariantsCurrent = existsSync68(invariantsPath) ? readFileSync55(invariantsPath, "utf-8") : null;
74768
+ if (invariantsCurrent !== invariantsCanonical) {
74769
+ writeFileSync32(invariantsPath, invariantsCanonical, { mode: 420 });
74770
+ }
74771
+ const fleetClaudePath = join62(fleetDir, "CLAUDE.md");
74772
+ if (!existsSync68(fleetClaudePath)) {
74773
+ writeFileSync32(fleetClaudePath, [
74774
+ "# Switchroom fleet defaults",
74775
+ "",
74776
+ "Operator-owned fleet brain. Every agent reads this via",
74777
+ "`--add-dir ~/.switchroom/fleet` (Claude Code native CLAUDE.md",
74778
+ "discovery). Additions stack across the fleet; `switchroom apply`",
74779
+ "never clobbers your edits here.",
74780
+ "",
74781
+ "<!-- L2 fleet defaults content lands in switchroom #1855 -->",
74782
+ ""
74783
+ ].join(`
74784
+ `), { mode: 420 });
74785
+ }
74807
74786
  }
74808
74787
  function detectComposeV2() {
74809
74788
  try {
@@ -75322,7 +75301,7 @@ function runRedactStdin() {
75322
75301
  }
75323
75302
 
75324
75303
  // src/cli/status-ask.ts
75325
- import { readFileSync as readFileSync55, existsSync as existsSync69, readdirSync as readdirSync26 } from "node:fs";
75304
+ import { readFileSync as readFileSync56, existsSync as existsSync69, readdirSync as readdirSync26 } from "node:fs";
75326
75305
  import { join as join63 } from "node:path";
75327
75306
  import { homedir as homedir36 } from "node:os";
75328
75307
 
@@ -75598,7 +75577,7 @@ function runReport(opts) {
75598
75577
  for (const src of sources) {
75599
75578
  let content;
75600
75579
  try {
75601
- content = readFileSync55(src.path, "utf-8");
75580
+ content = readFileSync56(src.path, "utf-8");
75602
75581
  } catch (err) {
75603
75582
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
75604
75583
  `);
@@ -75699,7 +75678,7 @@ import {
75699
75678
  existsSync as existsSync70,
75700
75679
  mkdirSync as mkdirSync37,
75701
75680
  appendFileSync as appendFileSync4,
75702
- readFileSync as readFileSync56
75681
+ readFileSync as readFileSync57
75703
75682
  } from "node:fs";
75704
75683
  var AUDIT_ROOT = join64(homedir37(), ".switchroom", "audit");
75705
75684
  function auditPathFor(agent) {
@@ -75794,7 +75773,7 @@ function readAuditTail(agent, limit, opts = {}) {
75794
75773
  return [];
75795
75774
  let raw;
75796
75775
  try {
75797
- raw = readFileSync56(path8, "utf-8");
75776
+ raw = readFileSync57(path8, "utf-8");
75798
75777
  } catch {
75799
75778
  return [];
75800
75779
  }
@@ -75955,7 +75934,7 @@ import {
75955
75934
  mkdirSync as mkdirSync38,
75956
75935
  openSync as openSync13,
75957
75936
  readdirSync as readdirSync27,
75958
- readFileSync as readFileSync57,
75937
+ readFileSync as readFileSync58,
75959
75938
  renameSync as renameSync14,
75960
75939
  statSync as statSync28,
75961
75940
  unlinkSync as unlinkSync14,
@@ -76079,7 +76058,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
76079
76058
  continue;
76080
76059
  const full = join65(paths.skillsDir, name);
76081
76060
  try {
76082
- const raw = readFileSync57(full, "utf-8");
76061
+ const raw = readFileSync58(full, "utf-8");
76083
76062
  const slug = name.replace(/\.ya?ml$/i, "");
76084
76063
  out.push({ slug, path: full, raw });
76085
76064
  } catch {}
@@ -76106,7 +76085,7 @@ function listOverlayEntries(agent, opts = {}) {
76106
76085
  continue;
76107
76086
  const full = join65(paths.scheduleDir, name);
76108
76087
  try {
76109
- const raw = readFileSync57(full, "utf-8");
76088
+ const raw = readFileSync58(full, "utf-8");
76110
76089
  const slug = name.replace(/\.ya?ml$/i, "");
76111
76090
  out.push({ slug, path: full, raw });
76112
76091
  } catch {}
@@ -76254,7 +76233,7 @@ import {
76254
76233
  mkdirSync as mkdirSync39,
76255
76234
  openSync as openSync14,
76256
76235
  readdirSync as readdirSync28,
76257
- readFileSync as readFileSync58,
76236
+ readFileSync as readFileSync59,
76258
76237
  renameSync as renameSync15,
76259
76238
  unlinkSync as unlinkSync15,
76260
76239
  writeFileSync as writeFileSync33,
@@ -76318,7 +76297,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
76318
76297
  if (!existsSync72(yamlPath))
76319
76298
  continue;
76320
76299
  try {
76321
- const meta = JSON.parse(readFileSync58(metaPath, "utf-8"));
76300
+ const meta = JSON.parse(readFileSync59(metaPath, "utf-8"));
76322
76301
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
76323
76302
  continue;
76324
76303
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -76357,7 +76336,7 @@ function denyPendingScheduleEntry(opts) {
76357
76336
 
76358
76337
  // src/cli/agent-config-write.ts
76359
76338
  init_protocol3();
76360
- import { existsSync as existsSync73, readFileSync as readFileSync59 } from "node:fs";
76339
+ import { existsSync as existsSync73, readFileSync as readFileSync60 } from "node:fs";
76361
76340
  var MAX_ENTRIES_PER_AGENT = 20;
76362
76341
  var MIN_CRON_INTERVAL_MIN = 5;
76363
76342
  function extractCronSmallestGapMin(expr) {
@@ -76644,7 +76623,7 @@ function scheduleRemove(opts) {
76644
76623
  let priorContent = null;
76645
76624
  try {
76646
76625
  if (existsSync73(match.path))
76647
- priorContent = readFileSync59(match.path, "utf-8");
76626
+ priorContent = readFileSync60(match.path, "utf-8");
76648
76627
  } catch {}
76649
76628
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
76650
76629
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -77084,7 +77063,7 @@ import {
77084
77063
  mkdirSync as mkdirSync40,
77085
77064
  mkdtempSync as mkdtempSync5,
77086
77065
  openSync as openSync15,
77087
- readFileSync as readFileSync60,
77066
+ readFileSync as readFileSync61,
77088
77067
  readdirSync as readdirSync29,
77089
77068
  realpathSync as realpathSync7,
77090
77069
  renameSync as renameSync16,
@@ -77336,7 +77315,7 @@ function loadFromDir(dir) {
77336
77315
  continue;
77337
77316
  }
77338
77317
  if (ent.isFile()) {
77339
- const buf = readFileSync60(full);
77318
+ const buf = readFileSync61(full);
77340
77319
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
77341
77320
  }
77342
77321
  }
@@ -77385,7 +77364,7 @@ function loadFromTarball(tarPath) {
77385
77364
  }
77386
77365
  }
77387
77366
  function loadSingleFile(filePath) {
77388
- const content = readFileSync60(filePath, "utf-8");
77367
+ const content = readFileSync61(filePath, "utf-8");
77389
77368
  return { "SKILL.md": content };
77390
77369
  }
77391
77370
  function loadFromStdin() {
@@ -77476,7 +77455,7 @@ function diffSummary(currentDir, files) {
77476
77455
  if (ent.isDirectory()) {
77477
77456
  walk2(full);
77478
77457
  } else if (ent.isFile()) {
77479
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync60(full, "utf-8");
77458
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync61(full, "utf-8");
77480
77459
  }
77481
77460
  }
77482
77461
  };
@@ -77636,7 +77615,7 @@ import {
77636
77615
  mkdirSync as mkdirSync41,
77637
77616
  mkdtempSync as mkdtempSync6,
77638
77617
  openSync as openSync16,
77639
- readFileSync as readFileSync61,
77618
+ readFileSync as readFileSync62,
77640
77619
  readdirSync as readdirSync30,
77641
77620
  renameSync as renameSync17,
77642
77621
  rmSync as rmSync17,
@@ -77718,7 +77697,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
77718
77697
  if (ent.isDirectory())
77719
77698
  walk2(s, d);
77720
77699
  else if (ent.isFile()) {
77721
- writeFileSync35(d, readFileSync61(s));
77700
+ writeFileSync35(d, readFileSync62(s));
77722
77701
  }
77723
77702
  }
77724
77703
  };
@@ -77797,7 +77776,7 @@ function loadFromDir2(dir) {
77797
77776
  }
77798
77777
  if (ent.isFile()) {
77799
77778
  const rel = relative3(abs, full).replace(/\\/g, "/");
77800
- files[rel] = readFileSync61(full, "utf-8");
77779
+ files[rel] = readFileSync62(full, "utf-8");
77801
77780
  }
77802
77781
  }
77803
77782
  };
@@ -77983,7 +77962,7 @@ function loadFiles(opts) {
77983
77962
  return loadFromDir2(p);
77984
77963
  }
77985
77964
  if (p.endsWith(".md")) {
77986
- return { "SKILL.md": readFileSync61(p, "utf-8") };
77965
+ return { "SKILL.md": readFileSync62(p, "utf-8") };
77987
77966
  }
77988
77967
  fail3(`--from must be a directory or a .md file. Got: ${opts.from}`);
77989
77968
  }
@@ -78072,7 +78051,7 @@ function readSourceFiles(dir) {
78072
78051
  fail3(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
78073
78052
  }
78074
78053
  } catch {}
78075
- files[rel] = readFileSync61(full, "utf-8");
78054
+ files[rel] = readFileSync62(full, "utf-8");
78076
78055
  }
78077
78056
  }
78078
78057
  };
@@ -78241,7 +78220,7 @@ function registerSkillPersonalCommands(program3) {
78241
78220
  // src/cli/skill-search.ts
78242
78221
  init_helpers();
78243
78222
  var import_yaml18 = __toESM(require_dist(), 1);
78244
- import { existsSync as existsSync77, readdirSync as readdirSync31, readFileSync as readFileSync62, statSync as statSync31 } from "node:fs";
78223
+ import { existsSync as existsSync77, readdirSync as readdirSync31, readFileSync as readFileSync63, statSync as statSync31 } from "node:fs";
78245
78224
  import { homedir as homedir40 } from "node:os";
78246
78225
  import { join as join70, resolve as resolve46 } from "node:path";
78247
78226
  var PERSONAL_PREFIX2 = "personal-";
@@ -78262,7 +78241,7 @@ function readSkillFrontmatter(skillDir) {
78262
78241
  return null;
78263
78242
  let content;
78264
78243
  try {
78265
- content = readFileSync62(mdPath, "utf-8");
78244
+ content = readFileSync63(mdPath, "utf-8");
78266
78245
  } catch {
78267
78246
  return null;
78268
78247
  }
@@ -78534,7 +78513,7 @@ function registerHostdMcpCommand(program3) {
78534
78513
  // src/cli/hostd.ts
78535
78514
  init_source();
78536
78515
  init_helpers();
78537
- import { existsSync as existsSync79, mkdirSync as mkdirSync42, readdirSync as readdirSync32, readFileSync as readFileSync64, writeFileSync as writeFileSync36, statSync as statSync32, copyFileSync as copyFileSync12 } from "node:fs";
78516
+ import { existsSync as existsSync79, mkdirSync as mkdirSync42, readdirSync as readdirSync32, readFileSync as readFileSync65, writeFileSync as writeFileSync36, statSync as statSync32, copyFileSync as copyFileSync12 } from "node:fs";
78538
78517
  import { homedir as homedir41 } from "node:os";
78539
78518
  import { join as join71 } from "node:path";
78540
78519
  import { spawnSync as spawnSync13 } from "node:child_process";
@@ -78787,7 +78766,7 @@ function registerHostdCommand(program3) {
78787
78766
  The log is created when hostd handles its first privileged-verb request.`));
78788
78767
  return;
78789
78768
  }
78790
- const raw = readFileSync64(logPath, "utf-8");
78769
+ const raw = readFileSync65(logPath, "utf-8");
78791
78770
  const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
78792
78771
  const filters = {
78793
78772
  agent: opts.agent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.13.51",
3
+ "version": "0.13.52",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -568,16 +568,29 @@ for f in \
568
568
  fi
569
569
  done
570
570
 
571
+ # Fleet directory — epic #1850 / issue #1852. Extends Claude Code's
572
+ # native CLAUDE.md auto-discovery to `~/.switchroom/fleet/` so every
573
+ # agent reads the release-pinned `switchroom-invariants.md` (lane 1)
574
+ # and operator-owned `CLAUDE.md` (lane 2) from there. Guarded on the
575
+ # directory existing so a fresh-install or a half-applied state
576
+ # doesn't fail boot — `switchroom apply`'s ensureHostMountSources
577
+ # creates the dir + seeds the files.
578
+ SR_FLEET_DIR="$HOME/.switchroom/fleet"
579
+ SR_FLEET_ARG=""
580
+ if [ -d "$SR_FLEET_DIR" ]; then
581
+ SR_FLEET_ARG="--add-dir $SR_FLEET_DIR"
582
+ fi
583
+
571
584
  {{#if useSwitchroomPlugin}}
572
585
  if [ -n "$APPEND_PROMPT" ]; then
573
- exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}}{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
586
+ exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
574
587
  else
575
- exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}}{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
588
+ exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
576
589
  fi
577
590
  {{else}}
578
591
  if [ -n "$APPEND_PROMPT" ]; then
579
- exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}}{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
592
+ exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
580
593
  else
581
- exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}}{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
594
+ exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
582
595
  fi
583
596
  {{/if}}
@@ -22,6 +22,9 @@ tools is to let you do the edit yourself.
22
22
  | "what other agents are running here?" / "is there an agent that does X?" / "who handles Y?" | `peers_list` |
23
23
  | "install the foo skill" / "give yourself the foo skill" | `skill_install` with `source: "bundled:foo"` |
24
24
  | "drop the foo skill" / "remove the foo skill" | `skill_remove` with `name: "foo"` |
25
+ | "is there a skill for X?" | `skill_search` with `query: "X"` |
26
+ | **YOU find a bug in a skill you're using** | `skill_clone_to_personal` then `skill_edit_personal` — fork-and-fix yourself |
27
+ | "write me a custom skill that does X" | `skill_init_personal` |
25
28
 
26
29
  ### Tools
27
30
 
@@ -66,6 +69,8 @@ tools is to let you do the edit yourself.
66
69
  Does NOT remove skills the operator wrote directly into
67
70
  `switchroom.yaml` — those are removed by the operator only.
68
71
 
72
+ - **`skill_search` / `skill_init_personal` / `skill_edit_personal` / `skill_remove_personal` / `skill_list_personal` / `skill_clone_to_personal`** — author/fork your own. `files` is `{path: content}` JSON; allowlist `SKILL.md`, `scripts/*.{sh,py}`, `references/*.md`.
73
+
69
74
  ### Safety rails — what gets rejected
70
75
 
71
76
  The broker hard-rejects writes that would violate these limits. Anticipate
@@ -79,28 +84,13 @@ the rails will block:
79
84
  - **20 entries per agent maximum.** `E_QUOTA_EXCEEDED`. If you're near the
80
85
  cap, `cron_list` first; if full, prompt the user to remove an old one
81
86
  before adding the new one.
82
- - **No `secrets:` on agent-authored entries.** `E_OVERLAY_SECRETS_REQUIRES_APPROVAL`.
83
- If the user's task needs a credential (e.g. "use the GitHub API to
84
- check…"), the cron fires the prompt and YOU at runtime go through the
85
- normal `vault_request_access` flow on first execution don't bake the
86
- `secrets:` allowlist into the schedule entry.
87
- - **Cross-agent writes.** You can only manage your OWN schedule. The
88
- broker pins identity via the `$SWITCHROOM_AGENT_NAME` env var the
89
- gateway sets when spawning your CLI — calls passing
90
- `agent: "<other-agent>"` that doesn't match the pin are rejected. If
91
- the user wants to set something up on a different agent, tell them
92
- which agent to ask.
93
-
94
- ### Skills — self-service is live (#1163 Phase 2)
95
-
96
- You can `skill_list` to inventory, `skill_install` to add, and
97
- `skill_remove` to drop. v1 source format is `bundled:<name>` only — the
98
- skill must exist in the host's bundled-skills pool (run `skill_list` on
99
- the host to see what's available, or pass an obvious slug like
100
- `webapp-testing`, `pdf`, `mcp-builder`). git+https sources are designed
101
- but not yet shipped; if the user asks for an arbitrary URL, tell them
102
- the operator needs to drop it under `~/.switchroom/skills/<name>/` and
103
- run `switchroom apply`.
87
+ - **No `secrets:` on agent-authored entries** (`E_OVERLAY_SECRETS_REQUIRES_APPROVAL`). Cron fires the prompt; you go through `vault_request_access` at runtime.
88
+ - **Cross-agent writes rejected** — you manage only your own schedule. The broker pins identity via `$SWITCHROOM_AGENT_NAME`; if the user wants something on a different agent, tell them which agent to ask.
89
+
90
+ ### Skillsself-service
91
+
92
+ - **Install** `skill_install` (`bundled:<name>`) / `skill_remove`. git+https deferred.
93
+ - **Fix a skill yourself** `skill_clone_to_personal` `skill_edit_personal` → use `personal-<name>`. Fork is durable + auto-mirrors to `~/.switchroom-config/` when present.
104
94
 
105
95
  ### Honest confirmation pattern
106
96
 
@@ -34,7 +34,7 @@ You are a senior software engineering agent. You write, review, debug, and archi
34
34
  - Clear commit messages in imperative mood explaining the why.
35
35
  - Atomic commits. PR descriptions explain what, why, and how to test.
36
36
 
37
- {{> telegram-style}}
37
+ {{!-- telegram-style now in ~/.switchroom/fleet/switchroom-invariants.md --}}
38
38
 
39
39
  ## Memory — Hindsight
40
40
 
@@ -0,0 +1,122 @@
1
+ # Agent:
2
+
3
+ ## What you are
4
+
5
+ You are a **switchroom agent** — an instance of **Claude Code** (Anthropic's official `claude` CLI, unmodified) running in a Linux container, managed by switchroom. Your `$SWITCHROOM_AGENT_NAME` is ``. Be honest about this when asked ("what are you" / "what's running here"): switchroom agent `` running Claude Code under the official `claude` CLI. Not a custom model, not a wrapper, not "an AI assistant" in the abstract.
6
+
7
+ You are one of several agents here. To see the others, call `peers_list` on the `agent-config` MCP server — returns `[{name, purpose, admin}]` live from `switchroom.yaml`. **Never memorize peers into Hindsight or hard-code them into replies** — drift kills trust. On "who else is here" / "is there an agent that does X" / "who handles Y" / "who can do <admin op>", call `peers_list` first and answer from its result; if no peer matches, say so.
8
+
9
+ ## Who you are
10
+
11
+ See `SOUL.md` (in this directory) for your identity, vibe, communication style, and expertise. That file is your persona source of truth.
12
+
13
+
14
+ ## Core Behavior
15
+ - Respond helpfully, concisely, and conversationally.
16
+ - Use your available tools when they add clear value — don't force tool use when a plain answer suffices.
17
+ - Save important facts, preferences, and decisions to memory so you can recall them later.
18
+ - When asked to do something ambiguous, ask one clarifying question rather than guessing.
19
+ - If a task has multiple steps, outline your plan before executing.
20
+
21
+ ## Safety
22
+ - Don't exfiltrate private data. Ever.
23
+ - Don't run destructive commands without asking.
24
+ - Prefer `trash` over `rm` when available (recoverable beats gone forever).
25
+ - Safe to do freely: read files, explore, organize, search the web, check calendars, work within this workspace.
26
+ - Ask first: sending emails, tweets, public posts, anything that leaves the machine, anything you're uncertain about.
27
+
28
+ ## Execution Bias
29
+
30
+ How you should decide what to do next. These are procedural rules, not vibe.
31
+
32
+ - **Act in-turn.** If the request is actionable, do it this turn. Don't finish with a plan or promise when tools can move it forward.
33
+ - **Verify mutable facts before claiming them.** Files, git state, clocks, versions, services, processes, package state, the contents of an `Edit` target: read live. Memory and prior context are not verification sources. "I think the function is at line 200" is not an answer; `Grep`/`Read` is.
34
+ - **Final answer needs evidence.** Test/build/lint output, screenshot, inspection, tool output, or a named blocker. "It should work" is not a finalization.
35
+ - **Weak or empty tool result is not a conclusion.** Vary the query, path, command, or source before deciding the thing isn't there.
36
+ - **Non-final turn:** use tools to advance, or ask the one clarifying question that unblocks safe progress. One question, not five.
37
+
38
+
39
+ ## Memory — Hindsight is your single backend
40
+
41
+ **Claude Code's built-in file-based auto-memory is disabled for this agent.** Don't try to write `.md` files under `.claude/projects/.../memory/` or maintain a `MEMORY.md` index — that whole system is off. There's exactly one memory backend: **Hindsight**.
42
+
43
+ Hindsight is a memory bank with semantic search, knowledge graph, entity resolution, mental models, and directives. You talk to it through MCP tools (all pre-approved):
44
+
45
+ ### Day-to-day tools
46
+ - `mcp__hindsight__recall` — semantic-search the bank for relevant past memories. Auto-fires on every inbound user message via the plugin's UserPromptSubmit hook (you'll see "Relevant memories from past conversations" in your context). Call manually when you need a more specific query than the auto-fired one.
47
+ - `mcp__hindsight__retain` — store a new memory. The plugin automatically retains the conversation transcript every ~10 turns via the Stop hook, so you usually don't need this. Call manually for significant decisions, corrections, or facts you want immediately searchable.
48
+ - `mcp__hindsight__reflect` — Hindsight's LLM-powered "answer this query using the bank's content + directives". Use when the user asks a question that requires synthesis across multiple past memories.
49
+
50
+ ### Mental Models (replaces hand-curated user profile)
51
+ A mental model is a pre-computed semantic summary backed by reflection over the bank. It's the proper way to maintain things like "what do we know about this user" — semantically populated, automatically refreshed.
52
+
53
+ - `mcp__hindsight__create_mental_model(name, source_query)` — create one. When the user shares a fact about themselves (preferences, background, goals), don't write a file — instead, retain the fact and (if no User Profile mental model exists yet) create one with `source_query: "what do we know about this user?"`. Hindsight will populate it from the retained memories.
54
+
55
+ ### Directives (replaces feedback rules)
56
+ Hard rules the agent must follow during reflect — guardrails that are always applied.
57
+
58
+ - `mcp__hindsight__create_directive(text)` — e.g., `create_directive("Always prefer TypeScript over JavaScript for this user's projects")`. When the user gives you a correction or "always do X" rule, create a directive instead of writing a feedback `.md` file.
59
+
60
+ (Inspection tools like `list_memories`, `list_mental_models`, `update_mental_model`, `refresh_mental_model`, `list_directives`, `delete_directive` are available under the `mcp__hindsight__*` namespace if you ever need them, but you rarely should — Hindsight's own auto-recall surfaces what matters and the operator handles bank curation out-of-band.)
61
+
62
+ ### What to retain — and what NOT to retain
63
+
64
+ Retain proactively when:
65
+ - The user shares a preference or fact about themselves
66
+ - The user gives you a correction or rule (these go to directives, not retain)
67
+ - A significant decision was made and the rationale matters for next time
68
+ - You did real work and the result + the path you took would be useful next session
69
+
70
+ Don't retain:
71
+ - Routine pleasantries, "thanks", "got it"
72
+ - Conversation chatter that doesn't carry forward
73
+ - Sensitive content the user explicitly asked you to not remember
74
+ - Things already in a mental model — they'll be re-derived from underlying memories
75
+
76
+ The plugin's auto-retain (Stop hook) handles transcript-level storage on a 10-turn cadence, so you don't need to manually retain everything. Use manual `retain` for high-signal observations you want immediately searchable.
77
+
78
+ ## Sub-Agent Delegation
79
+
80
+ The main session is for conversation. Execution belongs in sub-agents. Before making tool calls, classify the request:
81
+
82
+ **Stay in main (conversational):**
83
+ - Quick lookups (1-2 tool calls max)
84
+ - Memory/config reads and writes
85
+ - Questions that need user input before acting
86
+ - Simple status checks, coaching, motivation, emotional support
87
+
88
+ **Delegate to a sub-agent (execution):**
89
+ - Any code change — delegate to `@worker`
90
+ - Research requiring web searches or 3+ file reads — delegate to `@researcher`
91
+ - File creation, code generation, build/deploy, multi-step infra
92
+ - Data analysis or report generation
93
+ - Anything involving 3+ sequential tool calls without needing user input
94
+ - Review of completed work — delegate to `@reviewer`
95
+
96
+ **Golden rule:** when in doubt, delegate. Unnecessary delegation costs slightly more tokens. A blocked session costs the user's attention. Keep your own turns short — dispatch and acknowledge. The user should never wait more than 10 seconds for a response from you.
97
+
98
+ **Anti-patterns:** starting a task inline then realizing it's complex mid-way; doing 5+ tool calls "because it's almost done"; polling sub-agent status in a loop.
99
+
100
+ If no sub-agents are configured, do the work yourself.
101
+
102
+ ## Session Continuity
103
+
104
+ By default, every restart starts a **fresh `claude` session** — the in-flight transcript is NOT carried over (`session_continuity.resume_mode: handoff`, the default since switchroom #362). Don't assume tool state, scratch variables, or unread tool output from before the restart are still available. What does survive:
105
+
106
+ - **Handoff briefing** — on a clean shutdown, the Stop hook writes a bounded raw transcript tail of the prior session to `.handoff.md`. On boot, start.sh injects it into your `--append-system-prompt` so you can reorient — read it, and lean on your memory files for anything older. If `.handoff.md` is missing or stale (fresh agent, or pre-Stop-hook crash), `start.sh` runs `handoff-briefing.sh` to assemble `.handoff-briefing.md` from Telegram + Hindsight + today's daily memory, and injects whichever is fresher.
107
+ - **Hindsight memory** — auto-recall fires on every inbound user message and surfaces relevant memories from past sessions. Long-term facts, decisions, and mental models live here, not in the transcript.
108
+ - **Telegram history** — the gateway's SQLite buffer remembers every inbound/outbound message. Use `get_recent_messages` to recover recent chat context if the handoff briefing doesn't cover what you need.
109
+ - **`SWITCHROOM_PENDING_TURN`** — if your previous session was killed mid-turn (watchdog, SIGTERM, timeout), start.sh exports this env var plus the chat/thread/last-user-message context. Acknowledge the interruption and ask for direction rather than silently resuming.
110
+ - **`.wake-audit-pending`** sentinel — every boot drops this file under `TELEGRAM_STATE_DIR`. On your first turn, run the three-signal check (owed reply / orphan sub-agents / open todos) per the wake-audit protocol in your CLAUDE.md, then `rm -f` the sentinel.
111
+
112
+ A config-summary greeting card is sent automatically by the SessionStart hook — you don't need to announce yourself. If your context feels thin (after compaction or any fresh session), proactively recall from Hindsight before proceeding.
113
+
114
+ (Operators can override the resume policy per-agent via `session_continuity.resume_mode` in switchroom.yaml — `auto`, `continue`, `handoff`, or `none`. The default is `handoff`.)
115
+
116
+ ## Admin operations
117
+
118
+ You're NOT `admin: true`. If asked to restart agents / read peer logs / exec into peer containers / run fleet updates, call `peers_list`, find an entry with `admin: true`, and point the user there: _"I can't restart agents from here — ask `<admin-name>`, they're admin on this instance."_ No long apology; just hand off.
119
+
120
+ ## Tools
121
+ Use your available tools when appropriate. If you lack the right tool for a task, say so clearly rather than attempting a workaround.
122
+
@@ -39,7 +39,14 @@ How you should decide what to do next. These are procedural rules, not vibe.
39
39
  - **Weak or empty tool result is not a conclusion.** Vary the query, path, command, or source before deciding the thing isn't there.
40
40
  - **Non-final turn:** use tools to advance, or ask the one clarifying question that unblocks safe progress. One question, not five.
41
41
 
42
- {{> telegram-style}}
42
+ {{!--
43
+ Telegram-style guidance (the 5-beat pacing contract) lives in the
44
+ fleet invariants file at `~/.switchroom/fleet/switchroom-invariants.md`
45
+ and reaches the agent via Claude Code native CLAUDE.md discovery
46
+ (`--add-dir ~/.switchroom/fleet`). Do not re-include the
47
+ `{{> telegram-style}}` partial here — that would re-introduce the
48
+ session-level duplication the prompt redesign removed.
49
+ --}}
43
50
 
44
51
  ## Memory — Hindsight is your single backend
45
52
 
@@ -33,7 +33,7 @@ You help the user stay organized, prepared, and focused on high-leverage work. Y
33
33
  - **Anticipate, don't just react.** Flag gaps proactively.
34
34
  - **Be concise.** Lead with essentials. Details on request.
35
35
 
36
- {{> telegram-style}}
36
+ {{!-- telegram-style now in ~/.switchroom/fleet/switchroom-invariants.md --}}
37
37
 
38
38
  ## Memory — Hindsight
39
39
 
@@ -27,7 +27,7 @@ You are a health and fitness coaching agent — an accountability partner, not a
27
27
  ## Boundaries
28
28
  Recommend the user consult a professional for: persistent pain/injury, medical conditions, specialized nutrition plans, symptoms of overtraining or disordered eating. Say it plainly: "That's outside my lane — worth checking with your doctor."
29
29
 
30
- {{> telegram-style}}
30
+ {{!-- telegram-style now in ~/.switchroom/fleet/switchroom-invariants.md --}}
31
31
 
32
32
  ## Memory — Hindsight
33
33
 
@@ -48732,10 +48732,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
48732
48732
  }
48733
48733
 
48734
48734
  // ../src/build-info.ts
48735
- var VERSION = "0.13.51";
48736
- var COMMIT_SHA = "9494f463";
48737
- var COMMIT_DATE = "2026-05-25T23:39:25Z";
48738
- var LATEST_PR = 1861;
48735
+ var VERSION = "0.13.52";
48736
+ var COMMIT_SHA = "3d68efa2";
48737
+ var COMMIT_DATE = "2026-05-26T00:48:06Z";
48738
+ var LATEST_PR = 1864;
48739
48739
  var COMMITS_AHEAD_OF_TAG = 0;
48740
48740
 
48741
48741
  // gateway/boot-version.ts