switchroom 0.21.14 → 0.21.15

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.
@@ -11149,6 +11149,7 @@ var init_schema = __esm(() => {
11149
11149
  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
11150
  }).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
11151
  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)."),
11152
+ 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
11153
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
11153
11154
  observation_scopes: ObservationScopesSchema,
11154
11155
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -11197,7 +11198,8 @@ var init_schema = __esm(() => {
11197
11198
  }).optional().describe("Auto-recall tuning knobs"),
11198
11199
  retain: exports_external.object({
11199
11200
  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.")
11201
+ 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
+ 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
11203
  }).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
11202
11204
  }).optional();
11203
11205
  HookEntrySchema = exports_external.object({
@@ -11585,6 +11587,7 @@ var init_schema = __esm(() => {
11585
11587
  isolation: exports_external.enum(["default", "strict"]).optional(),
11586
11588
  profile: exports_external.string().optional(),
11587
11589
  directive_capture_nudge: exports_external.boolean().optional(),
11590
+ profile_capture_nudge: exports_external.boolean().optional(),
11588
11591
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
11589
11592
  observation_scopes: ObservationScopesSchema,
11590
11593
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -11633,7 +11636,8 @@ var init_schema = __esm(() => {
11633
11636
  }).optional(),
11634
11637
  retain: exports_external.object({
11635
11638
  every_n_turns: exports_external.number().int().min(1).optional(),
11636
- overlap_turns: exports_external.number().int().min(0).optional()
11639
+ overlap_turns: exports_external.number().int().min(0).optional(),
11640
+ tool_calls: exports_external.boolean().optional()
11637
11641
  }).optional(),
11638
11642
  bank_mission: exports_external.string().optional(),
11639
11643
  reflect_mission: exports_external.string().optional(),
@@ -21588,7 +21592,7 @@ function allocateAgentUid(name) {
21588
21592
  }
21589
21593
 
21590
21594
  // src/build-info.ts
21591
- var VERSION = "0.21.14";
21595
+ var VERSION = "0.21.15";
21592
21596
 
21593
21597
  // src/setup/hindsight-recall-passthrough.ts
21594
21598
  var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
@@ -4589,6 +4589,7 @@ var init_schema = __esm(() => {
4589
4589
  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
4590
  }).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
4591
  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)."),
4592
+ 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
4593
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
4593
4594
  observation_scopes: ObservationScopesSchema,
4594
4595
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -4637,7 +4638,8 @@ var init_schema = __esm(() => {
4637
4638
  }).optional().describe("Auto-recall tuning knobs"),
4638
4639
  retain: exports_external.object({
4639
4640
  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.")
4641
+ 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
+ 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
4643
  }).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
4642
4644
  }).optional();
4643
4645
  HookEntrySchema = exports_external.object({
@@ -5025,6 +5027,7 @@ var init_schema = __esm(() => {
5025
5027
  isolation: exports_external.enum(["default", "strict"]).optional(),
5026
5028
  profile: exports_external.string().optional(),
5027
5029
  directive_capture_nudge: exports_external.boolean().optional(),
5030
+ profile_capture_nudge: exports_external.boolean().optional(),
5028
5031
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
5029
5032
  observation_scopes: ObservationScopesSchema,
5030
5033
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -5073,7 +5076,8 @@ var init_schema = __esm(() => {
5073
5076
  }).optional(),
5074
5077
  retain: exports_external.object({
5075
5078
  every_n_turns: exports_external.number().int().min(1).optional(),
5076
- overlap_turns: exports_external.number().int().min(0).optional()
5079
+ overlap_turns: exports_external.number().int().min(0).optional(),
5080
+ tool_calls: exports_external.boolean().optional()
5077
5081
  }).optional(),
5078
5082
  bank_mission: exports_external.string().optional(),
5079
5083
  reflect_mission: exports_external.string().optional(),
@@ -4185,6 +4185,7 @@ var init_schema = __esm(() => {
4185
4185
  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
4186
  }).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
4187
  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)."),
4188
+ 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
4189
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
4189
4190
  observation_scopes: ObservationScopesSchema,
4190
4191
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -4233,7 +4234,8 @@ var init_schema = __esm(() => {
4233
4234
  }).optional().describe("Auto-recall tuning knobs"),
4234
4235
  retain: exports_external.object({
4235
4236
  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.")
4237
+ 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
+ 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
4239
  }).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
4238
4240
  }).optional();
4239
4241
  HookEntrySchema = exports_external.object({
@@ -4621,6 +4623,7 @@ var init_schema = __esm(() => {
4621
4623
  isolation: exports_external.enum(["default", "strict"]).optional(),
4622
4624
  profile: exports_external.string().optional(),
4623
4625
  directive_capture_nudge: exports_external.boolean().optional(),
4626
+ profile_capture_nudge: exports_external.boolean().optional(),
4624
4627
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
4625
4628
  observation_scopes: ObservationScopesSchema,
4626
4629
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -4669,7 +4672,8 @@ var init_schema = __esm(() => {
4669
4672
  }).optional(),
4670
4673
  retain: exports_external.object({
4671
4674
  every_n_turns: exports_external.number().int().min(1).optional(),
4672
- overlap_turns: exports_external.number().int().min(0).optional()
4675
+ overlap_turns: exports_external.number().int().min(0).optional(),
4676
+ tool_calls: exports_external.boolean().optional()
4673
4677
  }).optional(),
4674
4678
  bank_mission: exports_external.string().optional(),
4675
4679
  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.15",
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
@@ -21803,6 +21803,7 @@ var init_schema = __esm(() => {
21803
21803
  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
21804
  }).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
21805
  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)."),
21806
+ 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
21807
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
21807
21808
  observation_scopes: ObservationScopesSchema,
21808
21809
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -21851,7 +21852,8 @@ var init_schema = __esm(() => {
21851
21852
  }).optional().describe("Auto-recall tuning knobs"),
21852
21853
  retain: exports_external.object({
21853
21854
  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.")
21855
+ 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
+ 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
21857
  }).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
21856
21858
  }).optional();
21857
21859
  HookEntrySchema = exports_external.object({
@@ -22239,6 +22241,7 @@ var init_schema = __esm(() => {
22239
22241
  isolation: exports_external.enum(["default", "strict"]).optional(),
22240
22242
  profile: exports_external.string().optional(),
22241
22243
  directive_capture_nudge: exports_external.boolean().optional(),
22244
+ profile_capture_nudge: exports_external.boolean().optional(),
22242
22245
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
22243
22246
  observation_scopes: ObservationScopesSchema,
22244
22247
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -22287,7 +22290,8 @@ var init_schema = __esm(() => {
22287
22290
  }).optional(),
22288
22291
  retain: exports_external.object({
22289
22292
  every_n_turns: exports_external.number().int().min(1).optional(),
22290
- overlap_turns: exports_external.number().int().min(0).optional()
22293
+ overlap_turns: exports_external.number().int().min(0).optional(),
22294
+ tool_calls: exports_external.boolean().optional()
22291
22295
  }).optional(),
22292
22296
  bank_mission: exports_external.string().optional(),
22293
22297
  reflect_mission: exports_external.string().optional(),
@@ -105866,10 +105870,10 @@ function startOutboxSweep(deps) {
105866
105870
  }
105867
105871
 
105868
105872
  // ../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;
105873
+ var VERSION2 = "0.21.15";
105874
+ var COMMIT_SHA = "efa77e51";
105875
+ var COMMIT_DATE = "2026-08-17T00:42:23Z";
105876
+ var LATEST_PR = 4753;
105873
105877
  var COMMITS_AHEAD_OF_TAG = 0;
105874
105878
 
105875
105879
  // gateway/boot-version.ts
@@ -178,6 +178,7 @@ class HindsightClient:
178
178
  tags_match: Optional[str] = None,
179
179
  tag_groups: Optional[object] = None,
180
180
  prefer_observations: Optional[bool] = None,
181
+ query_timestamp: Optional[str] = None,
181
182
  timeout: int = 10,
182
183
  ) -> dict:
183
184
  """Recall memories from a bank.
@@ -190,6 +191,17 @@ class HindsightClient:
190
191
  `prefer_observations=True` asks the engine to prefer deduped
191
192
  observation statements over the raw facts they supersede, backfilling
192
193
  the freed slots — denser coverage inside the same token/count budget.
194
+
195
+ ``query_timestamp`` is an ISO 8601 datetime naming when the query is
196
+ being asked, from the user's perspective. The engine uses it as the
197
+ anchor for resolving relative temporal expressions in the query ("last
198
+ week", "yesterday") and for recency scoring; absent, the server's own
199
+ current time is the anchor. Sent only when non-empty, so a ``None``
200
+ (the default) leaves the wire body byte-identical to a pre-field
201
+ client — the additive-field invariant switchroom P2 depends on. The
202
+ REST recall body validates this field's format (a malformed value
203
+ 400s: "Invalid query_timestamp format. Expected ISO format"), so the
204
+ caller is responsible for passing a well-formed ISO string.
193
205
  """
194
206
  path = f"/v1/default/banks/{urllib.parse.quote(bank_id, safe='')}/memories/recall"
195
207
  body = {
@@ -208,6 +220,8 @@ class HindsightClient:
208
220
  body["tag_groups"] = tag_groups
209
221
  if prefer_observations is not None:
210
222
  body["prefer_observations"] = prefer_observations
223
+ if query_timestamp:
224
+ body["query_timestamp"] = query_timestamp
211
225
  return self._request("POST", path, body, timeout=timeout)
212
226
 
213
227
  def retain(
@@ -122,6 +122,14 @@ DEFAULTS = {
122
122
  # out per-agent via memory.directive_capture_nudge=false →
123
123
  # HINDSIGHT_DIRECTIVE_CAPTURE_NUDGE (disables BOTH hooks).
124
124
  "directiveCaptureNudge": True,
125
+ # RFC phase4 P3 — operator-profile capture nudge (recall.py,
126
+ # UserPromptSubmit). Regex-detects a first-person durable self-statement by
127
+ # the operator and appends a terse advisory telling the model to persist it
128
+ # with an explicit retain tagged `profile:ken` into the agent's OWN bank.
129
+ # Pure detection — no model callsite, no silent hook-side write. Operators
130
+ # opt out per-agent via memory.profile_capture_nudge=false →
131
+ # HINDSIGHT_PROFILE_CAPTURE_NUDGE.
132
+ "profileCaptureNudge": True,
125
133
  # Switchroom #2873/#2903 Fix 6.2 — the BLOCKING half (Stage C
126
134
  # directive_verify.py Stop hook) split out from the advisory nudge. When
127
135
  # True (default) the verifier may block the stop once to re-prompt capture;
@@ -426,6 +434,15 @@ ENV_OVERRIDES = {
426
434
  "HINDSIGHT_RETAIN_OVERLAP_TURNS": ("retainOverlapTurns", int),
427
435
  "HINDSIGHT_RETAIN_CONTEXT": ("retainContext", str),
428
436
  "HINDSIGHT_RETAIN_TAGS": ("retainTags", list),
437
+ # `retainToolCalls` (RFC memory-redesign P4): whether retain stores tool_use
438
+ # inputs + tool_result content. Had a DEFAULTS entry (True) but — like the
439
+ # cadence knobs before them — no env channel and no scaffold stamp, so an
440
+ # operator could not opt an agent out and a docker-exec'd retain/backfill
441
+ # could not be steered. Adding the env key mirrors the yaml surface
442
+ # (`memory.retain.tool_calls`) and closes the same drift class. `false`
443
+ # (via `false`/`0`/`no`) resolves to Python False and lands over the True
444
+ # default; unset keeps True, byte-identical.
445
+ "HINDSIGHT_RETAIN_TOOL_CALLS": ("retainToolCalls", bool),
429
446
  # Switchroom-local: per-row observation scope on retains. Set by start.sh
430
447
  # from agents.<name>.memory.observation_scopes (cascading through
431
448
  # defaults.memory.observation_scopes) ONLY when the operator set it; unset
@@ -488,6 +505,11 @@ ENV_OVERRIDES = {
488
505
  # the operator overrode it; the switchroom default is on (settings.json
489
506
  # pins true; recall.py falls back to True).
490
507
  "HINDSIGHT_DIRECTIVE_CAPTURE_NUDGE": ("directiveCaptureNudge", bool),
508
+ # RFC phase4 P3: operator-profile capture nudge on/off. Set by start.sh from
509
+ # agents.<name>.memory.profile_capture_nudge only when the operator overrode
510
+ # it; the switchroom default is on (settings.json pins true; recall.py falls
511
+ # back to True).
512
+ "HINDSIGHT_PROFILE_CAPTURE_NUDGE": ("profileCaptureNudge", bool),
491
513
  # Switchroom #2873/#2903 Fix 6.2: the Stage C block on/off, independent of
492
514
  # the Stage B nudge. Set by start.sh from
493
515
  # agents.<name>.memory.directive_capture_verify only when the operator
@@ -47,8 +47,9 @@ from .state import list_state_names, read_state, remove_state, write_state
47
47
  # move the doctor thresholds with it.
48
48
  #
49
49
  # Banks with more active directives than this are pathological; we truncate
50
- # with an in-prompt footer, a `directives_omitted` field on the recall_log row,
51
- # and a stderr warning (see `format_active_directives_block`).
50
+ # with a LOUD, operator-directed in-prompt overflow notice (memory-RFC P7 — the
51
+ # in-turn channel), a `directives_omitted` field on the recall_log row, and a
52
+ # stderr warning (see `format_active_directives_block`).
52
53
  MAX_DIRECTIVES = 30
53
54
 
54
55
  # Hard timeout for the list_directives call. The recall hook is on the
@@ -272,6 +273,10 @@ def format_active_directives_block(directives: list, max_directives: int = MAX_D
272
273
  1. [P10] <name>: <content>
273
274
  2. [P9] <name>: <content>
274
275
  ...
276
+
277
+ === DIRECTIVE OVERFLOW — ACTION REQUIRED ===
278
+ <N of TOTAL directives dropped; agent is told to surface it to the
279
+ operator this turn and merge/retire directives> (only when omitted > 0)
275
280
  (+N more, omitted)
276
281
  </active_directives>
277
282
  """
@@ -301,12 +306,45 @@ def format_active_directives_block(directives: list, max_directives: int = MAX_D
301
306
  lines.append(f"{i}. [P{priority}] {name}: {content}")
302
307
 
303
308
  if omitted > 0:
309
+ # LOUD in-prompt overflow notice (memory-RFC P7). The prior footer was a
310
+ # single quiet parenthetical — `(+N more, omitted)` — which the agent
311
+ # could read past without registering that explicit, operator-authored
312
+ # instructions had been silently DROPPED from this turn. The three other
313
+ # signals this module records are either not operator-visible (stderr —
314
+ # swallowed, see below) or not in-turn (the `directives_omitted`
315
+ # recall_log row and `switchroom doctor`, both read after the fact). The
316
+ # in-prompt block is the one channel that is BOTH in-turn and reaches an
317
+ # operator — indirectly, because the agent relays it in its reply. So we
318
+ # make the footer loud and explicitly action-directed: state the loss,
319
+ # name the count and cap, and instruct the agent to surface it to the
320
+ # operator THIS turn. This is the P7 "loud channel" and is deliberately
321
+ # NOT a MAX_DIRECTIVES change (raising the cap only moves the silent-drop
322
+ # point; visibility has to ship first).
323
+ #
324
+ # The literal `(+N more, omitted)` marker is retained verbatim so the
325
+ # existing recall_log/doctor cross-checks and the directive-dedup parser
326
+ # (`parse_active_directives_block`) keep working unchanged.
304
327
  lines.append("")
328
+ lines.append("=== DIRECTIVE OVERFLOW — ACTION REQUIRED ===")
329
+ lines.append(
330
+ f"{omitted} of {total} active directives were DROPPED from this turn's "
331
+ f"prompt: the bank exceeds MAX_DIRECTIVES={max_directives}, so the "
332
+ f"{omitted} LOWEST-priority directive(s) are NOT in effect this turn. "
333
+ "This is silent loss of explicit, operator-authored instructions."
334
+ )
335
+ lines.append(
336
+ "ACTION: tell the operator in your reply this turn that directive "
337
+ "overflow is dropping rules, then merge or retire duplicate/stale "
338
+ "directives (mental-model-curator skill) to get the bank back under "
339
+ "the cap. Do not let this pass unmentioned."
340
+ )
305
341
  lines.append(f"(+{omitted} more, omitted)")
306
- # The in-prompt footer above only tells the AGENT. This stderr warn is
307
- # the same channel every other operational failure in this module uses
308
- # (see `_fetch_directives_with_status`), and it is a LAST-RESORT
309
- # breadcrumb only — do NOT rely on it reaching an operator.
342
+ # The in-prompt notice above is the operator-visible in-turn channel
343
+ # (P7): the agent reads it every turn the overflow persists and is told
344
+ # to relay it. This stderr warn is the same channel every other
345
+ # operational failure in this module uses (see
346
+ # `_fetch_directives_with_status`), and it is a LAST-RESORT breadcrumb
347
+ # only — do NOT rely on it reaching an operator.
310
348
  #
311
349
  # Measured 2026-07-25: `docker logs --tail 20000` across all 12 running
312
350
  # agent containers returns ZERO `[Hindsight]` lines, and nothing under
@@ -315,7 +353,7 @@ def format_active_directives_block(directives: list, max_directives: int = MAX_D
315
353
  # appears to swallow hook stderr on a zero exit, so hook stderr is not
316
354
  # an operator-visible channel.
317
355
  #
318
- # The channels that DO reach an operator:
356
+ # The other channels that reach an operator, but only AFTER the turn:
319
357
  # * the `directives_omitted` field on the recall_log row
320
358
  # (state/recall_log.jsonl — see `count_omitted_directives`), and
321
359
  # * `switchroom doctor`'s WARN/FAIL on the bank's active directive
@@ -92,6 +92,33 @@ def _lock_path(session_id: str) -> str:
92
92
  return os.path.join(watermark_dir(), f"{_safe_session(session_id)}.lock")
93
93
 
94
94
 
95
+ def tail_after(messages: list, last_uuid: Optional[str]) -> list:
96
+ """Return the transcript entries AFTER the committed watermark anchor.
97
+
98
+ Pure and IO-free — the single shared slice used by BOTH the boot reconciler
99
+ (``reconcile_tail``) and the incremental SessionEnd sweep (``retain``, the
100
+ switchroom memory-RFC P1 change). One implementation, so the reconcile /
101
+ watermark failure category cannot grow a second, divergent copy.
102
+
103
+ Two safety fallbacks, both returning the WHOLE transcript — a safe
104
+ re-upsert, never a skip, never an empty slice:
105
+
106
+ * ``last_uuid`` falsy (no committed watermark) — e.g. a short session that
107
+ never fired a per-window retain, so the force sweep must flush it whole.
108
+ * ``last_uuid`` absent from ``messages`` (compaction removed the anchor).
109
+
110
+ Callers in the retain seam depend on the whole-transcript fallback: an empty
111
+ or skipped slice there DELETES a turn rather than degrading it
112
+ (``retain.py`` §4.3 hazard). Never change a fallback here to return ``[]``.
113
+ """
114
+ if not last_uuid:
115
+ return list(messages)
116
+ for i, m in enumerate(messages):
117
+ if isinstance(m, dict) and m.get("uuid") == last_uuid:
118
+ return messages[i + 1:]
119
+ return list(messages)
120
+
121
+
95
122
  def load(session_id: str) -> Optional[dict]:
96
123
  """Return the stored watermark dict for ``session_id``, or ``None``."""
97
124
  p = _path(session_id)