switchroom 0.21.14 → 0.21.16

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 (36) hide show
  1. package/bin/rules-sentinel-hook.sh +101 -0
  2. package/dist/agent-scheduler/index.js +7 -2
  3. package/dist/auth-broker/index.js +7 -2
  4. package/dist/cli/notion-write-pretool.mjs +7 -2
  5. package/dist/cli/switchroom.js +2601 -1009
  6. package/dist/host-control/main.js +8 -3
  7. package/dist/vault/approvals/kernel-server.js +7 -2
  8. package/dist/vault/broker/server.js +7 -2
  9. package/package.json +1 -1
  10. package/profiles/_base/start.sh.hbs +9 -0
  11. package/profiles/_shared/delegation-golden-rule.md.hbs +2 -0
  12. package/skills/mental-model-curator/SKILL.md +187 -56
  13. package/telegram-plugin/dist/gateway/gateway.js +11 -6
  14. package/vendor/hindsight-memory/hooks/hooks.json +10 -0
  15. package/vendor/hindsight-memory/scripts/lib/client.py +14 -0
  16. package/vendor/hindsight-memory/scripts/lib/config.py +22 -0
  17. package/vendor/hindsight-memory/scripts/lib/directives.py +45 -7
  18. package/vendor/hindsight-memory/scripts/lib/recall_buffer.py +236 -0
  19. package/vendor/hindsight-memory/scripts/lib/watermark.py +27 -0
  20. package/vendor/hindsight-memory/scripts/prefetch.py +156 -0
  21. package/vendor/hindsight-memory/scripts/recall.py +520 -5
  22. package/vendor/hindsight-memory/scripts/reconcile_tail.py +4 -12
  23. package/vendor/hindsight-memory/scripts/retain.py +167 -28
  24. package/vendor/hindsight-memory/scripts/tests/test_config_retain_tool_calls_env.py +98 -0
  25. package/vendor/hindsight-memory/scripts/tests/test_directives.py +52 -0
  26. package/vendor/hindsight-memory/scripts/tests/test_incremental_sweep.py +293 -0
  27. package/vendor/hindsight-memory/scripts/tests/test_prefetch_pipeline.py +247 -0
  28. package/vendor/hindsight-memory/scripts/tests/test_profile_capture_nudge.py +335 -0
  29. package/vendor/hindsight-memory/scripts/tests/test_recall_buffer.py +143 -0
  30. package/vendor/hindsight-memory/scripts/tests/test_recall_buffer_join.py +193 -0
  31. package/vendor/hindsight-memory/scripts/tests/test_recall_cap_truncation.py +133 -0
  32. package/vendor/hindsight-memory/scripts/tests/test_recall_junk_gate.py +168 -0
  33. package/vendor/hindsight-memory/scripts/tests/test_recall_no_score_floor.py +124 -0
  34. package/vendor/hindsight-memory/scripts/tests/test_recall_query_timestamp.py +376 -0
  35. package/vendor/hindsight-memory/scripts/tests/test_retain_delta.py +304 -0
  36. package/vendor/hindsight-memory/scripts/tests/test_retain_stop_hook_prefetch_gate.py +109 -0
@@ -11143,12 +11143,14 @@ var init_schema = __esm(() => {
11143
11143
  }
11144
11144
  }).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry — e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced — 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded — each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
11145
11145
  observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
11146
+ rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded — the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false ⇒ byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded — each agent's flip is a deliberate M3 rollout step)."),
11146
11147
  disposition: exports_external.object({
11147
11148
  skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
11148
11149
  literalism: exports_external.number().int().min(1).max(5).optional().describe("How literally the bank reads statements vs inferring intent (1-5; engine default 3)."),
11149
11150
  empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
11150
11151
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
11151
11152
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
11153
+ profile_capture_nudge: exports_external.boolean().optional().describe("Deterministic operator-profile capture nudge (RFC phase4 P3, serves " + 'the "save memories about him" want). When on (switchroom default ' + "true), the auto-recall hook regex-detects a first-person durable " + 'self-statement by the operator ("I prefer …", "my … is …", "I ' + 'always …", "remind me that I …") and appends a terse advisory to ' + "the turn's context telling the model to persist it with an explicit " + "mcp__hindsight__retain tagged `profile:ken` into the agent's OWN bank " + "(not a shared/cross-agent person bank). Detection is pure regex — the " + "model does the judgment in-session and calls retain itself (no model " + "callsite, no silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
11152
11154
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
11153
11155
  observation_scopes: ObservationScopesSchema,
11154
11156
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -11197,7 +11199,8 @@ var init_schema = __esm(() => {
11197
11199
  }).optional().describe("Auto-recall tuning knobs"),
11198
11200
  retain: exports_external.object({
11199
11201
  every_n_turns: exports_external.number().int().min(1).optional().describe("How often the Stop hook fires auto-retention, in turns. The " + "vendor plugin default is 10 (a short session can end before " + "retention ever fires); switchroom's scaffold default is 3. " + "Lower = more frequent, smaller retains + tighter crash " + "durability (at 1, every turn is retained before a restart can " + "lose it); higher = fewer, larger retains + less LLM churn. " + "Raised from the historical 1 to 3 because the local reasoning " + "consolidation model (Ollama gpt-oss-20b) ran away on the large " + "overlapping every-turn payloads. Set to 1 for the old " + "every-turn crash-durability guarantee. Min 1. Cascade: " + "per-field merge (agent wins over default)."),
11200
- overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge.")
11202
+ overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge."),
11203
+ tool_calls: exports_external.boolean().optional().describe("Whether auto-retain stores assistant tool_use blocks (with their " + "entire input dict) and tool_result content (up to 2000 chars) " + "alongside the human/assistant turns. Vendor + switchroom default " + "is true — the fleet behaves byte-identically until an operator " + "sets this false. Tool inputs/results are a large fraction of " + "retained volume (RFC memory-redesign P4) but also where evidence " + "lives (a commit SHA, a failing test's output, the diff that " + "fixed something), so the tradeoff is real and per-agent. false is " + "not a new mode: sidechains already force it off " + "(subagent_retain.py). Set false to drop tool exhaust from an " + "agent's retains. Cascade: per-field merge (agent wins over " + "default).")
11201
11204
  }).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
11202
11205
  }).optional();
11203
11206
  HookEntrySchema = exports_external.object({
@@ -11585,6 +11588,7 @@ var init_schema = __esm(() => {
11585
11588
  isolation: exports_external.enum(["default", "strict"]).optional(),
11586
11589
  profile: exports_external.string().optional(),
11587
11590
  directive_capture_nudge: exports_external.boolean().optional(),
11591
+ profile_capture_nudge: exports_external.boolean().optional(),
11588
11592
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
11589
11593
  observation_scopes: ObservationScopesSchema,
11590
11594
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -11633,7 +11637,8 @@ var init_schema = __esm(() => {
11633
11637
  }).optional(),
11634
11638
  retain: exports_external.object({
11635
11639
  every_n_turns: exports_external.number().int().min(1).optional(),
11636
- overlap_turns: exports_external.number().int().min(0).optional()
11640
+ overlap_turns: exports_external.number().int().min(0).optional(),
11641
+ tool_calls: exports_external.boolean().optional()
11637
11642
  }).optional(),
11638
11643
  bank_mission: exports_external.string().optional(),
11639
11644
  reflect_mission: exports_external.string().optional(),
@@ -21588,7 +21593,7 @@ function allocateAgentUid(name) {
21588
21593
  }
21589
21594
 
21590
21595
  // src/build-info.ts
21591
- var VERSION = "0.21.14";
21596
+ var VERSION = "0.21.16";
21592
21597
 
21593
21598
  // src/setup/hindsight-recall-passthrough.ts
21594
21599
  var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
@@ -4583,12 +4583,14 @@ var init_schema = __esm(() => {
4583
4583
  }
4584
4584
  }).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry — e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced — 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded — each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
4585
4585
  observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
4586
+ rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded — the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false ⇒ byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded — each agent's flip is a deliberate M3 rollout step)."),
4586
4587
  disposition: exports_external.object({
4587
4588
  skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
4588
4589
  literalism: exports_external.number().int().min(1).max(5).optional().describe("How literally the bank reads statements vs inferring intent (1-5; engine default 3)."),
4589
4590
  empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
4590
4591
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
4591
4592
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
4593
+ profile_capture_nudge: exports_external.boolean().optional().describe("Deterministic operator-profile capture nudge (RFC phase4 P3, serves " + 'the "save memories about him" want). When on (switchroom default ' + "true), the auto-recall hook regex-detects a first-person durable " + 'self-statement by the operator ("I prefer …", "my … is …", "I ' + 'always …", "remind me that I …") and appends a terse advisory to ' + "the turn's context telling the model to persist it with an explicit " + "mcp__hindsight__retain tagged `profile:ken` into the agent's OWN bank " + "(not a shared/cross-agent person bank). Detection is pure regex — the " + "model does the judgment in-session and calls retain itself (no model " + "callsite, no silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
4592
4594
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
4593
4595
  observation_scopes: ObservationScopesSchema,
4594
4596
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -4637,7 +4639,8 @@ var init_schema = __esm(() => {
4637
4639
  }).optional().describe("Auto-recall tuning knobs"),
4638
4640
  retain: exports_external.object({
4639
4641
  every_n_turns: exports_external.number().int().min(1).optional().describe("How often the Stop hook fires auto-retention, in turns. The " + "vendor plugin default is 10 (a short session can end before " + "retention ever fires); switchroom's scaffold default is 3. " + "Lower = more frequent, smaller retains + tighter crash " + "durability (at 1, every turn is retained before a restart can " + "lose it); higher = fewer, larger retains + less LLM churn. " + "Raised from the historical 1 to 3 because the local reasoning " + "consolidation model (Ollama gpt-oss-20b) ran away on the large " + "overlapping every-turn payloads. Set to 1 for the old " + "every-turn crash-durability guarantee. Min 1. Cascade: " + "per-field merge (agent wins over default)."),
4640
- overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge.")
4642
+ overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge."),
4643
+ tool_calls: exports_external.boolean().optional().describe("Whether auto-retain stores assistant tool_use blocks (with their " + "entire input dict) and tool_result content (up to 2000 chars) " + "alongside the human/assistant turns. Vendor + switchroom default " + "is true — the fleet behaves byte-identically until an operator " + "sets this false. Tool inputs/results are a large fraction of " + "retained volume (RFC memory-redesign P4) but also where evidence " + "lives (a commit SHA, a failing test's output, the diff that " + "fixed something), so the tradeoff is real and per-agent. false is " + "not a new mode: sidechains already force it off " + "(subagent_retain.py). Set false to drop tool exhaust from an " + "agent's retains. Cascade: per-field merge (agent wins over " + "default).")
4641
4644
  }).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
4642
4645
  }).optional();
4643
4646
  HookEntrySchema = exports_external.object({
@@ -5025,6 +5028,7 @@ var init_schema = __esm(() => {
5025
5028
  isolation: exports_external.enum(["default", "strict"]).optional(),
5026
5029
  profile: exports_external.string().optional(),
5027
5030
  directive_capture_nudge: exports_external.boolean().optional(),
5031
+ profile_capture_nudge: exports_external.boolean().optional(),
5028
5032
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
5029
5033
  observation_scopes: ObservationScopesSchema,
5030
5034
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -5073,7 +5077,8 @@ var init_schema = __esm(() => {
5073
5077
  }).optional(),
5074
5078
  retain: exports_external.object({
5075
5079
  every_n_turns: exports_external.number().int().min(1).optional(),
5076
- overlap_turns: exports_external.number().int().min(0).optional()
5080
+ overlap_turns: exports_external.number().int().min(0).optional(),
5081
+ tool_calls: exports_external.boolean().optional()
5077
5082
  }).optional(),
5078
5083
  bank_mission: exports_external.string().optional(),
5079
5084
  reflect_mission: exports_external.string().optional(),
@@ -4179,12 +4179,14 @@ var init_schema = __esm(() => {
4179
4179
  }
4180
4180
  }).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry — e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced — 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded — each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
4181
4181
  observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
4182
+ rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded — the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false ⇒ byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded — each agent's flip is a deliberate M3 rollout step)."),
4182
4183
  disposition: exports_external.object({
4183
4184
  skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
4184
4185
  literalism: exports_external.number().int().min(1).max(5).optional().describe("How literally the bank reads statements vs inferring intent (1-5; engine default 3)."),
4185
4186
  empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
4186
4187
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
4187
4188
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
4189
+ profile_capture_nudge: exports_external.boolean().optional().describe("Deterministic operator-profile capture nudge (RFC phase4 P3, serves " + 'the "save memories about him" want). When on (switchroom default ' + "true), the auto-recall hook regex-detects a first-person durable " + 'self-statement by the operator ("I prefer …", "my … is …", "I ' + 'always …", "remind me that I …") and appends a terse advisory to ' + "the turn's context telling the model to persist it with an explicit " + "mcp__hindsight__retain tagged `profile:ken` into the agent's OWN bank " + "(not a shared/cross-agent person bank). Detection is pure regex — the " + "model does the judgment in-session and calls retain itself (no model " + "callsite, no silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
4188
4190
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
4189
4191
  observation_scopes: ObservationScopesSchema,
4190
4192
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -4233,7 +4235,8 @@ var init_schema = __esm(() => {
4233
4235
  }).optional().describe("Auto-recall tuning knobs"),
4234
4236
  retain: exports_external.object({
4235
4237
  every_n_turns: exports_external.number().int().min(1).optional().describe("How often the Stop hook fires auto-retention, in turns. The " + "vendor plugin default is 10 (a short session can end before " + "retention ever fires); switchroom's scaffold default is 3. " + "Lower = more frequent, smaller retains + tighter crash " + "durability (at 1, every turn is retained before a restart can " + "lose it); higher = fewer, larger retains + less LLM churn. " + "Raised from the historical 1 to 3 because the local reasoning " + "consolidation model (Ollama gpt-oss-20b) ran away on the large " + "overlapping every-turn payloads. Set to 1 for the old " + "every-turn crash-durability guarantee. Min 1. Cascade: " + "per-field merge (agent wins over default)."),
4236
- overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge.")
4238
+ overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge."),
4239
+ tool_calls: exports_external.boolean().optional().describe("Whether auto-retain stores assistant tool_use blocks (with their " + "entire input dict) and tool_result content (up to 2000 chars) " + "alongside the human/assistant turns. Vendor + switchroom default " + "is true — the fleet behaves byte-identically until an operator " + "sets this false. Tool inputs/results are a large fraction of " + "retained volume (RFC memory-redesign P4) but also where evidence " + "lives (a commit SHA, a failing test's output, the diff that " + "fixed something), so the tradeoff is real and per-agent. false is " + "not a new mode: sidechains already force it off " + "(subagent_retain.py). Set false to drop tool exhaust from an " + "agent's retains. Cascade: per-field merge (agent wins over " + "default).")
4237
4240
  }).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
4238
4241
  }).optional();
4239
4242
  HookEntrySchema = exports_external.object({
@@ -4621,6 +4624,7 @@ var init_schema = __esm(() => {
4621
4624
  isolation: exports_external.enum(["default", "strict"]).optional(),
4622
4625
  profile: exports_external.string().optional(),
4623
4626
  directive_capture_nudge: exports_external.boolean().optional(),
4627
+ profile_capture_nudge: exports_external.boolean().optional(),
4624
4628
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
4625
4629
  observation_scopes: ObservationScopesSchema,
4626
4630
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -4669,7 +4673,8 @@ var init_schema = __esm(() => {
4669
4673
  }).optional(),
4670
4674
  retain: exports_external.object({
4671
4675
  every_n_turns: exports_external.number().int().min(1).optional(),
4672
- overlap_turns: exports_external.number().int().min(0).optional()
4676
+ overlap_turns: exports_external.number().int().min(0).optional(),
4677
+ tool_calls: exports_external.boolean().optional()
4673
4678
  }).optional(),
4674
4679
  bank_mission: exports_external.string().optional(),
4675
4680
  reflect_mission: exports_external.string().optional(),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "switchroom",
3
3
  "//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
4
- "version": "0.21.14",
4
+ "version": "0.21.16",
5
5
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -1189,6 +1189,15 @@ export HINDSIGHT_RECALL_SKIP_TRIVIAL={{hindsightRecallSkipTrivial}}
1189
1189
  {{#if hindsightDirectiveCaptureNudge}}
1190
1190
  export HINDSIGHT_DIRECTIVE_CAPTURE_NUDGE={{hindsightDirectiveCaptureNudge}}
1191
1191
  {{/if}}
1192
+ # RFC phase4 P3 — operator-profile capture nudge (memory.profile_capture_nudge
1193
+ # cascade). On by default (plugin settings.json): recall.py regex-detects a
1194
+ # first-person durable self-statement by the operator and nudges the model to
1195
+ # persist it with an explicit retain tagged `profile:ken` into the agent's own
1196
+ # bank. Export only when the operator overrode it; set false to disable the
1197
+ # nudge for this agent.
1198
+ {{#if hindsightProfileCaptureNudge}}
1199
+ export HINDSIGHT_PROFILE_CAPTURE_NUDGE={{hindsightProfileCaptureNudge}}
1200
+ {{/if}}
1192
1201
  # Per-row observation scope (memory.observation_scopes cascade). Stamped on
1193
1202
  # every retain — Stop hook, sidechain, boot reconcile, queue drain, backfill.
1194
1203
  # "shared" makes consolidation pool this agent's observations into ONE global
@@ -7,3 +7,5 @@ Acting in-turn and delegating are the same move: for an execution-class task the
7
7
  If the user amends in-flight delegated work mid-turn, steer the running worker now (`SendMessage` by worker name or agent id) rather than holding it for handback — and say in your reply which you did ("folded your update into the running worker" / "queued as a separate task"); never classify silently, never inferred. Unsure? Queue it and say so — queue is the default. If the worker is effectively done and the steer lands too late, say so and apply the update yourself in the parent.
8
8
 
9
9
  If no sub-agents are configured, do the work yourself.
10
+
11
+ A worker has no auto-recall of its own — it can only PULL its own bank read-only (`mcp__hindsight__recall` / `get_mental_model` / `get_knowledge_tree` / `get_knowledge_page` / `search_knowledge_pages`; M6/E-86). When you dispatch, put the orientation you already hold — the relevant roster, bank names, or prior findings — into the Task prompt as your deliberate context selection. Share orientation, not secrets: never paste a credential or another agent's private fact into a dispatch prompt.
@@ -9,11 +9,12 @@ description: >
9
9
  "review my memory bank", "what standing models should I have", "bootstrap
10
10
  mental models", "suggest mental models from my bank", "audit my mental models",
11
11
  "what recurring questions do I keep re-deriving", and typos like "propose
12
- knowlege models". ALSO owns the directive merge/retire pass — triggers like
13
- "audit my directives", "merge my directives", "retire stale directives", "my
14
- directives are overlapping", "too many active directives", "clean up my
15
- guardrails", and when switchroom doctor FLAGs a bank over the directive WARN/FAIL
16
- threshold. Do NOT use for storing a single fact/preference/decision —
12
+ knowlege models". ALSO owns the directive TRIAGE pass (Memory v2 M2) —
13
+ triggers like "audit my directives", "triage my directives", "merge my
14
+ directives", "retire stale directives", "my directives are overlapping", "too
15
+ many active directives", "clean up my guardrails", and when switchroom doctor
16
+ FLAGs a bank over the directive WARN/FAIL threshold. Do NOT use for storing a
17
+ single fact/preference/decision —
17
18
  that is retain, not a mental model. Do NOT use for identity or "who is the
18
19
  user" — that lives in profile banks, so never propose an identity model here
19
20
  (the operator's review of the card, not any code guard, is the only gate — so
@@ -22,7 +23,7 @@ description: >
22
23
  whose shape you know, PROPOSE it through the approve/deny card — never call
23
24
  create_mental_model directly (it is not pre-approved; the propose card is the
24
25
  only sanctioned write path, Fix 1.2 / #2903).
25
- allowed-tools: mcp__hindsight__list_banks mcp__hindsight__get_bank_stats mcp__hindsight__list_mental_models mcp__hindsight__get_mental_model mcp__hindsight__list_directives mcp__hindsight__reflect mcp__hindsight__recall mcp__switchroom-telegram__mental_model_propose
26
+ allowed-tools: mcp__hindsight__list_banks mcp__hindsight__get_bank_stats mcp__hindsight__list_mental_models mcp__hindsight__get_mental_model mcp__hindsight__list_directives mcp__hindsight__deactivate_directive mcp__hindsight__reactivate_directive mcp__hindsight__reflect mcp__hindsight__recall mcp__switchroom-telegram__mental_model_propose Bash(switchroom memory directive mark-rules-block *)
26
27
  ---
27
28
 
28
29
  # mental-model-curator — Propose standing knowledge models from your own bank
@@ -58,9 +59,11 @@ models. Propose the few that are earned; let the operator ratify.
58
59
  (name + source_query + one-line reason) and STOP. Do NOT call
59
60
  `mental_model_propose` in this mode. This is the safe way to run against any
60
61
  bank.
61
- - **Directive merge/retire pass:** a separate job — see "Directive merge/retire
62
- pass" below. It is always proposal-only (text out; the operator taps to enact),
63
- and runs independently of the mental-model modes above.
62
+ - **Directive triage pass:** a separate job — see "Directive triage pass (Memory
63
+ v2 M2)" below. It renders one consolidated card as text, then requires an
64
+ explicit operator go-ahead before ANY deactivation executes — never enacts on
65
+ the same turn the card is shown — and runs independently of the mental-model
66
+ modes above.
64
67
 
65
68
  ## Workflow
66
69
 
@@ -152,23 +155,87 @@ empty topic at 3am is useless and noisy. If invoked from a scheduled sweep,
152
155
  the next interactive turn instead of firing cards unattended. Treat live-turn,
153
156
  operator-present proposing as the only supported path for now.
154
157
 
155
- ## Directive merge/retire pass
156
-
157
- A second, independent job of this skill: keep the bank's **active directives**
158
- lean. Directives are hard rules applied on every `reflect` — but the bank caps
159
- active directives at **`MAX_DIRECTIVES=30`**. Past the cap, the lowest-priority
160
- directives are **truncated** from the `<active_directives>` recall block and never
161
- reach the agent (recorded as `directives_omitted` on the recall_log row — the
162
- recall hook's stderr warning is swallowed by Claude Code, so don't look there) — so an
163
- overloaded, overlapping, or stale directive set doesn't just add noise, it drops
164
- your real guardrails. Fleet doctor WARNs at >24 active and FAILs at >30
165
- (workstream C2), and its fix text points
166
- here — this pass is the durable path it names.
167
-
168
- **You PROPOSE; you never delete.** `delete_directive` is deliberately NOT
169
- pre-approved (#2911) — it falls through to a per-call operator approval card, and
170
- that human tap is the only gate that retires a standing guardrail. So this pass
171
- outputs a plan; it does not enact retirements itself.
158
+ ## Directive triage pass (Memory v2 M2)
159
+
160
+ A second, independent job of this skill: sort the bank's **active directives**
161
+ into E-45's five categories and clear the four heavy banks off the WARN line.
162
+ Directives are hard rules applied on every `reflect` — but the bank caps active
163
+ directives at **`MAX_DIRECTIVES=30`**. Past the cap, the lowest-priority
164
+ directives are **truncated** from the `<active_directives>` recall block and
165
+ never reach the agent (recorded as `directives_omitted` on the recall_log row
166
+ — the recall hook's stderr warning is swallowed by Claude Code, so don't look
167
+ there). Fleet doctor WARNs at >24 active and FAILs at >30 (`doctor-memory.ts`,
168
+ `DIRECTIVE_WARN_THRESHOLD`), and its fix text points here.
169
+
170
+ **Honest framing (do not oversell):** as of the M2 measurement pass, NOTHING is
171
+ currently being silently dropped fleet-wide — zero directives have ever been
172
+ retired, and `directives_omitted` has fired zero times. This is a **budget +
173
+ hygiene** pass, not a rescue from silent drops. Say it that way; overstating the
174
+ urgency is its own failure.
175
+
176
+ **The five categories every active directive sorts into (E-45):**
177
+
178
+ | Category | Meaning | What happens in THIS pass |
179
+ |---|---|---|
180
+ | `reflect-directive` | A genuine compliance guardrail (§4.2's designed role) | KEEP — no change |
181
+ | `rules-block` | A standing preference that belongs in the CLAUDE.md rules block (M1) | **STAGE ONLY — stays ACTIVE.** See the hard rule below. |
182
+ | `disposition` | Better expressed as `disposition_*` bank config (E-40) | Deactivate; note the config change separately for the operator — do NOT auto-apply it |
183
+ | `retire` | Superseded or mechanized — dead weight | Deactivate (record `superseded_by` when a winner exists) |
184
+ | `retain-as-memory` | A fact filed as a directive by mistake (category error) | Deactivate — verify the fact already exists as a memory in the bank BEFORE proposing this; don't assert it, check it |
185
+
186
+ **Retirement signals are deterministic and bounded (E-45).** A directive is
187
+ ONLY a retirement candidate when you can point to one of: (a) it is tagged
188
+ `superseded-by:<name>` (or you would tag it that way, naming the winner), or
189
+ (b) a mechanization reference (a named commit/PR/scaffold rule that now does
190
+ this automatically), or (c) a documented category error you can verify against
191
+ the bank's own memories/config. **Citation counts are NEVER a signal** (a rule
192
+ that works quietly gets zero citations). **Age alone is NEVER sufficient.** A
193
+ directive with none of these signals is `reflect-directive` — KEEP — by
194
+ default. Never propose retiring a directive because it "seems old" or "looks
195
+ unused."
196
+
197
+ ### HARD RULE — never deactivate a `rules-block`-category directive in this pass
198
+
199
+ If a directive's behaviour is destined for the CLAUDE.md rules block (M1), its
200
+ migration happens at **M3 flip time**, when that surface goes live for this
201
+ agent — not now. M1 shipped dark (`memory.rules_block` off on every agent
202
+ today); deactivating a `rules-block`-category directive before that flip would
203
+ leave the agent with **neither** the directive (deactivated) **nor** the rule
204
+ (block not live) — a silent guardrail gap for however long the M2→M3 interval
205
+ runs. So: **for every directive you categorise `rules-block`, call
206
+ `list_directives` to confirm it, but do NOT call `deactivate_directive` on it
207
+ in this pass, ever** — only stage its text (mention it in the card as staged,
208
+ count it, and leave it exactly as-is) for the M3 flip to pick up later
209
+ (`rule add`, once `memory.rules_block` is on for this agent).
210
+
211
+ **This is not just a prose rule you have to remember — it is now also code-
212
+ enforced, but ONLY once you complete the mandatory step below.**
213
+ `DirectiveAdmin` (`src/memory/hindsight-directive-admin.ts` —
214
+ `deactivate`/`deactivateById`/`deactivateByIdWithTag`) refuses to deactivate
215
+ ANY directive carrying the persisted `triage-category:rules-block` marker
216
+ tag, on every call path, including this skill's own `deactivate_directive`
217
+ calls. But that refusal only ever fires for a directive that actually
218
+ CARRIES the tag — and nothing stamps the tag automatically just because you,
219
+ in your own reasoning, decided a directive is `rules-block`. **You must
220
+ stamp it yourself, immediately, as part of classification — not as an
221
+ afterthought before presenting the card:**
222
+
223
+ > For every directive you classify `rules-block` in step 2 below, before you
224
+ > move on to the next directive, run:
225
+ > ```
226
+ > switchroom memory directive mark-rules-block <agent> <id>
227
+ > ```
228
+ > (`src/cli/memory-directive.ts` — this calls the exact same
229
+ > `DirectiveAdmin.markRulesBlock` write the batch triage executor uses, so
230
+ > the chokepoint above keys off it identically). Idempotent — re-running it
231
+ > on an already-marked directive is a no-op, so it is always safe to call
232
+ > even if you are not sure whether a prior pass already marked this one.
233
+
234
+ Do this for EVERY rules-block row before step 3's card renders, not just the
235
+ ones you personally plan to leave alone — the whole point is that the code
236
+ guard, not your own discipline in this pass or a future one, is what refuses
237
+ a later deactivation attempt (including a mistaken one, or one made by a
238
+ different pass that never saw this reasoning).
172
239
 
173
240
  ### When to run it
174
241
 
@@ -178,40 +245,104 @@ outputs a plan; it does not enact retirements itself.
178
245
 
179
246
  ### Workflow
180
247
 
181
- 1. **List the active set.** `mcp__hindsight__list_directives` (active only). If
182
- the count is comfortably under the WARN threshold (≤24) AND nothing reads
248
+ 1. **List every directive, active and inactive context.** `mcp__hindsight__list_directives`
249
+ (active only is fine for triage — inactive ones are already off). If the
250
+ active count is comfortably under the WARN threshold (≤24) AND nothing reads
183
251
  stale/overlapping, STOP and report "directive set is healthy (N active) —
184
- nothing to merge or retire." Don't manufacture churn.
185
- 2. **Cluster for overlap.** Group directives that encode substantially the same
186
- rule (even reworded) or that are strict specializations of a broader one. Each
187
- cluster of 2+ is a **merge candidate**: one crisp consolidated directive that
188
- subsumes the group, with the retirements it replaces named.
189
- 3. **Flag the stale.** A directive is a **retire candidate** when it's obsoleted
190
- by a newer rule, references a decision/tool/project that no longer exists, or
191
- contradicts a directive you're keeping. Contradictions are the priority — a
192
- live contradictory pair makes `reflect` nondeterministic.
193
- 4. **Present the plan as text — do NOT enact.** Output, ranked by how much it
194
- reduces the active count and risk:
195
- - **Merges:** the proposed consolidated directive text + the exact directives
196
- (by name/id) it would retire.
197
- - **Retirements:** each stale directive (name/id) + one-line reason.
198
- - The resulting active count vs `MAX_DIRECTIVES`, so the operator sees whether
199
- the plan clears the cap.
200
- Then STOP. The operator decides. Enacting a merge is a follow-up: the
201
- consolidated `create_directive` is autonomous, but **each** retirement fires
202
- its own `delete_directive` approval card — that per-card human tap is the
203
- deterministic backstop, not something this skill routes around.
252
+ nothing to triage." Don't manufacture churn.
253
+ 2. **Classify every directive — no drops.** For each one, assign exactly one of
254
+ the five categories above, with the deterministic signal that justifies it
255
+ (or "no signal — KEEP" for the default case). Do this for the FULL set —
256
+ the card is worthless if a directive silently doesn't appear on it.
257
+ **The moment you classify a directive `rules-block`, run
258
+ `switchroom memory directive mark-rules-block <agent> <id>` for it right
259
+ then** (see the HARD RULE above) — do not defer this to a later step or
260
+ batch it at the end; every rules-block row must be stamped before you move
261
+ to step 3.
262
+ 3. **Render ONE consolidated card as text.** Two sections, KEEP/staged first,
263
+ retirement candidates second — never interleaved, so a skim of "the bottom
264
+ half" cannot land on a live guardrail:
265
+ - **Keep / staged (N):** every `reflect-directive` and `rules-block` row,
266
+ each showing priority + category + signal (or "no signal — default KEEP").
267
+ - **Retirement candidates (N):** every `disposition` / `retire` /
268
+ `retain-as-memory` row, each showing priority + category + the
269
+ deterministic signal — never a bare category with no rationale.
270
+ - The resulting active count vs `MAX_DIRECTIVES` if the retirements land.
271
+ 4. **STOP. Get an explicit operator go-ahead before touching anything.** Do not
272
+ deactivate on the same turn the card is shown. The card is the operator's
273
+ one look before a batch of guardrails changes state.
274
+ 5. **On go-ahead, retire ONE AT A TIME with a human tap PER retirement — never
275
+ a single blanket go-ahead for the whole batch.** `deactivate_directive` is
276
+ pre-approved fleet-wide (`scaffold.ts`'s 2026-07-29 amendment,
277
+ `HINDSIGHT_MCP_TOOLS`) — unlike `delete_directive`, calling it does NOT
278
+ raise a Telegram permission card by itself. That is exactly why this skill
279
+ must supply the per-call gate `delete_directive`'s card would otherwise
280
+ have given: present the NEXT retirement candidate (name, priority, signal,
281
+ proposed `superseded_by`) on its own, then **STOP and end the turn** —
282
+ do not proceed to it, and do not queue the next one, until the operator's
283
+ reply for THIS ONE arrives. Only after an explicit per-row confirmation do
284
+ you call `mcp__hindsight__deactivate_directive` for that single directive.
285
+ Then present the next candidate the same way. A single "yes, go ahead" that
286
+ covers the whole retirement list is NOT sufficient — that collapses back
287
+ to the batch-level prose go-ahead this per-row loop exists to replace, and
288
+ silently trades away the human tap `delete_directive`'s approval card used
289
+ to guarantee for every one of these retirements (see PR #4760 review B2).
290
+ Pass `superseded_by` when you named a winner. After the LAST retirement in
291
+ the plan lands, `list_directives` again and report the new active count
292
+ against `MAX_DIRECTIVES`, plus which directives moved where.
293
+ 6. **Disposition rows are a note, not an auto-edit.** When you deactivate a
294
+ `disposition`-category directive, separately flag to the operator which
295
+ `disposition_*` bank config change it corresponds to — do NOT edit bank
296
+ config yourself in this pass (E-40: audit before leaning on it).
297
+
298
+ ### windows-boxes-class fixes (a drifted directive with a peer's superset text) — special-cased, rare
299
+
300
+ Do NOT attempt this as an ordinary retirement. If a directive on this bank has
301
+ drifted from a peer agent's superset/reconciled version of the "same" rule
302
+ (content differs but the two should say the same thing), that is NOT
303
+ expressible as a `content`-PATCH — the shim deliberately refuses to PATCH
304
+ directive `content` (`hindsight-directive-admin.ts` — a directive body is the
305
+ guardrail itself; there is no history table to recover a bad rewrite from).
306
+ It is also NOT safely expressible as plain `create_directive` +
307
+ `deactivate_directive` calls from this skill: once the new copy is created
308
+ carrying the SAME name, `deactivate_directive`'s name-resolution sees two
309
+ directives sharing that name and refuses as ambiguous. Use
310
+ `reconcileDirectiveSuperset` (`src/memory/directive-triage-executor.ts`) for
311
+ this instead — it resolves the old copy's id BEFORE creating the new one and
312
+ retires it by id, so the name collision never becomes an ambiguity error. This
313
+ is NOT a bare MCP tool call from this skill (no MCP tool exposes an
314
+ id-targeted deactivate) — it runs via
315
+ `switchroom memory directive reconcile <agent> <name> <content...>`
316
+ (`src/cli/memory-directive.ts`), an operator or repo-authorized CLI/script
317
+ invocation, not something this skill invokes autonomously. Flag any drift you
318
+ notice to the operator rather than improvising a fix.
204
319
 
205
320
  ### Directive-pass anti-patterns
206
321
 
207
- - ❌ Calling `delete_directive` from this skill — it proposes; the operator's tap
208
- on the per-call card is the only sanctioned retire path.
209
- - ❌ Merging directives that only look similar — a merge that drops a real
210
- distinction silently weakens a guardrail. When unsure, propose retire-nothing.
211
- - ❌ Manufacturing merges on a healthy small set just to "tidy" — run the pass
212
- when it's earned (bloat, overlap, staleness, or a doctor flag), not reflexively.
213
- - ❌ Leaving a live contradictory pair in place — surface it as the top retire
214
- candidate; it's what makes `reflect` nondeterministic.
322
+ - ❌ Deactivating a `rules-block`-category directive before M3 flips this
323
+ agent — opens a guardrail gap; see the hard rule above.
324
+ - ❌ Classifying a directive `rules-block` without immediately calling
325
+ `switchroom memory directive mark-rules-block <agent> <id>` for it — an
326
+ unmarked directive gets NO code-level protection from the
327
+ `DirectiveAdmin` chokepoint; the classification in your head is not
328
+ enough.
329
+ - ❌ Retiring on age or citation count alone — E-45 refuses both as signals.
330
+ - ❌ Calling `deactivate_directive` in parallel / without awaiting each one —
331
+ sequential only (tag-write race).
332
+ - ❌ Executing on the same turn the card is shown — the card review is the
333
+ gate; always stop for an explicit go-ahead first.
334
+ - ❌ Treating one blanket "yes, go ahead" as clearance for the whole
335
+ retirement list — `deactivate_directive` is pre-approved (no Telegram card
336
+ per call), so this skill's own per-retirement stop-and-confirm loop IS the
337
+ human tap; batching it away silently weakens the guardrail (review B2).
338
+ - ❌ Auto-editing bank config for a `disposition` row — flag it, don't apply it.
339
+ - ❌ Treating a drifted/superset-text directive as an ordinary retirement —
340
+ see the windows-boxes-class section above.
341
+ - ❌ Manufacturing retirements on a healthy small set just to "tidy" — run the
342
+ pass when it's earned (bloat, overlap, staleness, or a doctor flag), not
343
+ reflexively.
344
+ - ❌ Leaving a live contradictory pair in place — surface it as the top
345
+ retirement candidate; it's what makes `reflect` nondeterministic.
215
346
 
216
347
  ## Anti-patterns
217
348
 
@@ -21797,12 +21797,14 @@ var init_schema = __esm(() => {
21797
21797
  }
21798
21798
  }).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry \u2014 e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced \u2014 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded \u2014 each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
21799
21799
  observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
21800
+ rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded \u2014 the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false \u21d2 byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded \u2014 each agent's flip is a deliberate M3 rollout step)."),
21800
21801
  disposition: exports_external.object({
21801
21802
  skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
21802
21803
  literalism: exports_external.number().int().min(1).max(5).optional().describe("How literally the bank reads statements vs inferring intent (1-5; engine default 3)."),
21803
21804
  empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
21804
21805
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis \u2014 a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
21805
21806
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true \u2014 Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never \u2026", "from now on \u2026", ' + `"stop doing \u2026", a stated preference, "that's wrong, it's \u2026") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex \u2014 the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
21807
+ profile_capture_nudge: exports_external.boolean().optional().describe("Deterministic operator-profile capture nudge (RFC phase4 P3, serves " + 'the "save memories about him" want). When on (switchroom default ' + "true), the auto-recall hook regex-detects a first-person durable " + 'self-statement by the operator ("I prefer \u2026", "my \u2026 is \u2026", "I ' + 'always \u2026", "remind me that I \u2026") and appends a terse advisory to ' + "the turn's context telling the model to persist it with an explicit " + "mcp__hindsight__retain tagged `profile:ken` into the agent's OWN bank " + "(not a shared/cross-agent person bank). Detection is pure regex \u2014 the " + "model does the judgment in-session and calls retain itself (no model " + "callsite, no silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
21806
21808
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
21807
21809
  observation_scopes: ObservationScopesSchema,
21808
21810
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -21851,7 +21853,8 @@ var init_schema = __esm(() => {
21851
21853
  }).optional().describe("Auto-recall tuning knobs"),
21852
21854
  retain: exports_external.object({
21853
21855
  every_n_turns: exports_external.number().int().min(1).optional().describe("How often the Stop hook fires auto-retention, in turns. The " + "vendor plugin default is 10 (a short session can end before " + "retention ever fires); switchroom's scaffold default is 3. " + "Lower = more frequent, smaller retains + tighter crash " + "durability (at 1, every turn is retained before a restart can " + "lose it); higher = fewer, larger retains + less LLM churn. " + "Raised from the historical 1 to 3 because the local reasoning " + "consolidation model (Ollama gpt-oss-20b) ran away on the large " + "overlapping every-turn payloads. Set to 1 for the old " + "every-turn crash-durability guarantee. Min 1. Cascade: " + "per-field merge (agent wins over default)."),
21854
- overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge.")
21856
+ overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge."),
21857
+ tool_calls: exports_external.boolean().optional().describe("Whether auto-retain stores assistant tool_use blocks (with their " + "entire input dict) and tool_result content (up to 2000 chars) " + "alongside the human/assistant turns. Vendor + switchroom default " + "is true \u2014 the fleet behaves byte-identically until an operator " + "sets this false. Tool inputs/results are a large fraction of " + "retained volume (RFC memory-redesign P4) but also where evidence " + "lives (a commit SHA, a failing test's output, the diff that " + "fixed something), so the tradeoff is real and per-agent. false is " + "not a new mode: sidechains already force it off " + "(subagent_retain.py). Set false to drop tool exhaust from an " + "agent's retains. Cascade: per-field merge (agent wins over " + "default).")
21855
21858
  }).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
21856
21859
  }).optional();
21857
21860
  HookEntrySchema = exports_external.object({
@@ -22239,6 +22242,7 @@ var init_schema = __esm(() => {
22239
22242
  isolation: exports_external.enum(["default", "strict"]).optional(),
22240
22243
  profile: exports_external.string().optional(),
22241
22244
  directive_capture_nudge: exports_external.boolean().optional(),
22245
+ profile_capture_nudge: exports_external.boolean().optional(),
22242
22246
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
22243
22247
  observation_scopes: ObservationScopesSchema,
22244
22248
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -22287,7 +22291,8 @@ var init_schema = __esm(() => {
22287
22291
  }).optional(),
22288
22292
  retain: exports_external.object({
22289
22293
  every_n_turns: exports_external.number().int().min(1).optional(),
22290
- overlap_turns: exports_external.number().int().min(0).optional()
22294
+ overlap_turns: exports_external.number().int().min(0).optional(),
22295
+ tool_calls: exports_external.boolean().optional()
22291
22296
  }).optional(),
22292
22297
  bank_mission: exports_external.string().optional(),
22293
22298
  reflect_mission: exports_external.string().optional(),
@@ -105866,10 +105871,10 @@ function startOutboxSweep(deps) {
105866
105871
  }
105867
105872
 
105868
105873
  // ../src/build-info.ts
105869
- var VERSION2 = "0.21.14";
105870
- var COMMIT_SHA = "2d43063c";
105871
- var COMMIT_DATE = "2026-08-16T08:34:12Z";
105872
- var LATEST_PR = 4741;
105874
+ var VERSION2 = "0.21.16";
105875
+ var COMMIT_SHA = "d6c1267a";
105876
+ var COMMIT_DATE = "2026-08-17T09:16:43Z";
105877
+ var LATEST_PR = 4763;
105873
105878
  var COMMITS_AHEAD_OF_TAG = 0;
105874
105879
 
105875
105880
  // gateway/boot-version.ts
@@ -42,6 +42,16 @@
42
42
  "async": true
43
43
  }
44
44
  ]
45
+ },
46
+ {
47
+ "hooks": [
48
+ {
49
+ "type": "command",
50
+ "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/scripts/prefetch.py\"",
51
+ "timeout": 20,
52
+ "async": true
53
+ }
54
+ ]
45
55
  }
46
56
  ],
47
57
  "SubagentStop": [