switchroom 0.21.16 → 0.21.17

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.
@@ -11119,6 +11119,10 @@ var AgentMemorySchema = exports_external.object({
11119
11119
  }).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)."),
11120
11120
  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."),
11121
11121
  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)."),
11122
+ orientation: exports_external.boolean().optional().describe("Memory v2 M5 go-live flag (Surface B: orientation-at-boot), " + "effective default false — dark build (unset ⇒ OFF, applied by the " + "scaffold resolver, NOT a Zod .default() — a hard default here would " + "shadow the per-agent value in the cascade the way it does every other " + "memory knob). When true, the orientation SessionStart " + "hook injects this agent's cron-refreshed `orientation` mental model " + "into context at boot AND re-seats it after every compaction (E-88), " + "deterministically and with zero tool call. Unset/false ⇒ byte-" + "identical to pre-M5 behaviour: the hook no-ops before any bank " + "resolve or network call. Per-agent ONLY (never fleet-seeded — a " + "flip is a deliberate canary/rollout step gated on the agent's " + "m2-residue measurement + operator-created model, carve-M5 §7). " + "Cascade: override (per-agent wins over default)."),
11123
+ orientation_reinject_turns: exports_external.number().int().min(0).optional().describe("Memory v2 M5 optional per-turn re-inject cadence, effective default " + "0 = off (unset ⇒ 0, applied by the scaffold resolver, not a Zod " + ".default() — same cascade-shadowing reason as `orientation`). At 0 the " + "briefing is injected only at SessionStart + compaction (the cheap " + "path). N>0 " + "would additionally re-inject the orientation briefing every N turns; " + "priced at ~55M tok/30d at every-turn, which is WHY it defaults off " + "(carve-M5 §2/§4). Only late-session context loss should ever set it, " + "per-agent, measured. A stripped/absent value fails to 0 (fail-safe " + "AND fail-cheap). Per-agent ONLY. Cascade: override."),
11124
+ orientation_cadence_hours: exports_external.number().int().min(1).optional().describe("Memory v2 M5 per-agent refresh cadence tier (hours), effective " + "default 48 (unset ⇒ 48, applied by the scaffold resolver, not a Zod " + ".default() — a hard default would shadow a `defaults.memory." + "orientation_cadence_hours` fleet value in the cascade). " + "The fast-churning agents (klanker, overlord) set 24; everyone else " + "inherits 48 (carve-M5 §6, Ken's locked tiered cadence). Drives BOTH " + "the out-of-session refresh scheduler AND the staleness guard's " + "per-tier thresholds (stale prefix at 1.5× cadence, cold degrade at " + "3×) — so a fixed 36h can never mislabel a 48h-tier agent as stale a " + "day early. Modelled as an explicit int (not a hard-coded name list) " + "so the tiering is data-driven and auditable. A cost knob, not an " + "enablement knob: a stripped/absent value failing to 48 is safe " + "(the cheaper tier). Cascade: override (per-agent wins over default); " + "unlike `orientation`/`_reinject_turns` it IS accepted at the " + "defaults/profile tier."),
11125
+ inject_directives: exports_external.boolean().optional().describe("Memory v2 M3 Surface-A directive-injection switch (default TRUE — " + "on-by-default via the plugin settings.json stamp + config.py " + "DEFAULTS). When left unset the agent keeps injecting the " + "`<active_directives>` block on every turn, byte-identical to pre-M3. " + "Set FALSE to SUPPRESS that injection for this agent — its standing " + "rules are then served from the live rules block (memory.rules_block) " + "instead of re-injected directives, which is the change that collapses " + "the always-on directive-token spend (E-41). The suppression is " + "fail-safe and re-checked every turn in recall.py: it fires ONLY when " + "a non-empty rules block is physically present in the agent's " + "CLAUDE.md — a false flag with an empty/absent block keeps injecting " + "AND emits a degraded-canary notice, so a zero-standing-rules turn is " + "unreachable. Requires memory.rules_block=true first (the block must " + "be live before injection is turned off — enforce the ordering with " + "`switchroom memory flip`, never a bare flag flip). Coordinates with " + "M4's memory.recall prefetch (memoryPrefetchEnabled): the same guard " + "gates all three recall.py emit sites so a flipped agent leaks no " + "directive on a cache/prefetch hit. Cascade: override, per-agent ONLY " + "(never fleet-seeded from the defaults/profile tier — each flip is a " + "deliberate staggered M3 rollout step, same darkness safeguard as " + "rules_block). Default-true is the fail-safe direction under the " + "Zod-strip footgun: a stripped/misread key keeps the agent injecting."),
11122
11126
  disposition: exports_external.object({
11123
11127
  skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
11124
11128
  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)."),
@@ -11564,6 +11568,7 @@ var profileFields = {
11564
11568
  profile: exports_external.string().optional(),
11565
11569
  directive_capture_nudge: exports_external.boolean().optional(),
11566
11570
  profile_capture_nudge: exports_external.boolean().optional(),
11571
+ orientation_cadence_hours: exports_external.number().int().min(1).optional(),
11567
11572
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
11568
11573
  observation_scopes: ObservationScopesSchema,
11569
11574
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -11144,6 +11144,10 @@ var init_schema = __esm(() => {
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
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)."),
11147
+ orientation: exports_external.boolean().optional().describe("Memory v2 M5 go-live flag (Surface B: orientation-at-boot), " + "effective default false — dark build (unset ⇒ OFF, applied by the " + "scaffold resolver, NOT a Zod .default() — a hard default here would " + "shadow the per-agent value in the cascade the way it does every other " + "memory knob). When true, the orientation SessionStart " + "hook injects this agent's cron-refreshed `orientation` mental model " + "into context at boot AND re-seats it after every compaction (E-88), " + "deterministically and with zero tool call. Unset/false ⇒ byte-" + "identical to pre-M5 behaviour: the hook no-ops before any bank " + "resolve or network call. Per-agent ONLY (never fleet-seeded — a " + "flip is a deliberate canary/rollout step gated on the agent's " + "m2-residue measurement + operator-created model, carve-M5 §7). " + "Cascade: override (per-agent wins over default)."),
11148
+ orientation_reinject_turns: exports_external.number().int().min(0).optional().describe("Memory v2 M5 optional per-turn re-inject cadence, effective default " + "0 = off (unset ⇒ 0, applied by the scaffold resolver, not a Zod " + ".default() — same cascade-shadowing reason as `orientation`). At 0 the " + "briefing is injected only at SessionStart + compaction (the cheap " + "path). N>0 " + "would additionally re-inject the orientation briefing every N turns; " + "priced at ~55M tok/30d at every-turn, which is WHY it defaults off " + "(carve-M5 §2/§4). Only late-session context loss should ever set it, " + "per-agent, measured. A stripped/absent value fails to 0 (fail-safe " + "AND fail-cheap). Per-agent ONLY. Cascade: override."),
11149
+ orientation_cadence_hours: exports_external.number().int().min(1).optional().describe("Memory v2 M5 per-agent refresh cadence tier (hours), effective " + "default 48 (unset ⇒ 48, applied by the scaffold resolver, not a Zod " + ".default() — a hard default would shadow a `defaults.memory." + "orientation_cadence_hours` fleet value in the cascade). " + "The fast-churning agents (klanker, overlord) set 24; everyone else " + "inherits 48 (carve-M5 §6, Ken's locked tiered cadence). Drives BOTH " + "the out-of-session refresh scheduler AND the staleness guard's " + "per-tier thresholds (stale prefix at 1.5× cadence, cold degrade at " + "3×) — so a fixed 36h can never mislabel a 48h-tier agent as stale a " + "day early. Modelled as an explicit int (not a hard-coded name list) " + "so the tiering is data-driven and auditable. A cost knob, not an " + "enablement knob: a stripped/absent value failing to 48 is safe " + "(the cheaper tier). Cascade: override (per-agent wins over default); " + "unlike `orientation`/`_reinject_turns` it IS accepted at the " + "defaults/profile tier."),
11150
+ inject_directives: exports_external.boolean().optional().describe("Memory v2 M3 Surface-A directive-injection switch (default TRUE — " + "on-by-default via the plugin settings.json stamp + config.py " + "DEFAULTS). When left unset the agent keeps injecting the " + "`<active_directives>` block on every turn, byte-identical to pre-M3. " + "Set FALSE to SUPPRESS that injection for this agent — its standing " + "rules are then served from the live rules block (memory.rules_block) " + "instead of re-injected directives, which is the change that collapses " + "the always-on directive-token spend (E-41). The suppression is " + "fail-safe and re-checked every turn in recall.py: it fires ONLY when " + "a non-empty rules block is physically present in the agent's " + "CLAUDE.md — a false flag with an empty/absent block keeps injecting " + "AND emits a degraded-canary notice, so a zero-standing-rules turn is " + "unreachable. Requires memory.rules_block=true first (the block must " + "be live before injection is turned off — enforce the ordering with " + "`switchroom memory flip`, never a bare flag flip). Coordinates with " + "M4's memory.recall prefetch (memoryPrefetchEnabled): the same guard " + "gates all three recall.py emit sites so a flipped agent leaks no " + "directive on a cache/prefetch hit. Cascade: override, per-agent ONLY " + "(never fleet-seeded from the defaults/profile tier — each flip is a " + "deliberate staggered M3 rollout step, same darkness safeguard as " + "rules_block). Default-true is the fail-safe direction under the " + "Zod-strip footgun: a stripped/misread key keeps the agent injecting."),
11147
11151
  disposition: exports_external.object({
11148
11152
  skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
11149
11153
  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)."),
@@ -11589,6 +11593,7 @@ var init_schema = __esm(() => {
11589
11593
  profile: exports_external.string().optional(),
11590
11594
  directive_capture_nudge: exports_external.boolean().optional(),
11591
11595
  profile_capture_nudge: exports_external.boolean().optional(),
11596
+ orientation_cadence_hours: exports_external.number().int().min(1).optional(),
11592
11597
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
11593
11598
  observation_scopes: ObservationScopesSchema,
11594
11599
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -11880,6 +11880,10 @@ var AgentMemorySchema = exports_external.object({
11880
11880
  }).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)."),
11881
11881
  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."),
11882
11882
  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)."),
11883
+ orientation: exports_external.boolean().optional().describe("Memory v2 M5 go-live flag (Surface B: orientation-at-boot), " + "effective default false \u2014 dark build (unset \u21d2 OFF, applied by the " + "scaffold resolver, NOT a Zod .default() \u2014 a hard default here would " + "shadow the per-agent value in the cascade the way it does every other " + "memory knob). When true, the orientation SessionStart " + "hook injects this agent's cron-refreshed `orientation` mental model " + "into context at boot AND re-seats it after every compaction (E-88), " + "deterministically and with zero tool call. Unset/false \u21d2 byte-" + "identical to pre-M5 behaviour: the hook no-ops before any bank " + "resolve or network call. Per-agent ONLY (never fleet-seeded \u2014 a " + "flip is a deliberate canary/rollout step gated on the agent's " + "m2-residue measurement + operator-created model, carve-M5 \u00a77). " + "Cascade: override (per-agent wins over default)."),
11884
+ orientation_reinject_turns: exports_external.number().int().min(0).optional().describe("Memory v2 M5 optional per-turn re-inject cadence, effective default " + "0 = off (unset \u21d2 0, applied by the scaffold resolver, not a Zod " + ".default() \u2014 same cascade-shadowing reason as `orientation`). At 0 the " + "briefing is injected only at SessionStart + compaction (the cheap " + "path). N>0 " + "would additionally re-inject the orientation briefing every N turns; " + "priced at ~55M tok/30d at every-turn, which is WHY it defaults off " + "(carve-M5 \u00a72/\u00a74). Only late-session context loss should ever set it, " + "per-agent, measured. A stripped/absent value fails to 0 (fail-safe " + "AND fail-cheap). Per-agent ONLY. Cascade: override."),
11885
+ orientation_cadence_hours: exports_external.number().int().min(1).optional().describe("Memory v2 M5 per-agent refresh cadence tier (hours), effective " + "default 48 (unset \u21d2 48, applied by the scaffold resolver, not a Zod " + ".default() \u2014 a hard default would shadow a `defaults.memory." + "orientation_cadence_hours` fleet value in the cascade). " + "The fast-churning agents (klanker, overlord) set 24; everyone else " + "inherits 48 (carve-M5 \u00a76, Ken's locked tiered cadence). Drives BOTH " + "the out-of-session refresh scheduler AND the staleness guard's " + "per-tier thresholds (stale prefix at 1.5\u00d7 cadence, cold degrade at " + "3\u00d7) \u2014 so a fixed 36h can never mislabel a 48h-tier agent as stale a " + "day early. Modelled as an explicit int (not a hard-coded name list) " + "so the tiering is data-driven and auditable. A cost knob, not an " + "enablement knob: a stripped/absent value failing to 48 is safe " + "(the cheaper tier). Cascade: override (per-agent wins over default); " + "unlike `orientation`/`_reinject_turns` it IS accepted at the " + "defaults/profile tier."),
11886
+ inject_directives: exports_external.boolean().optional().describe("Memory v2 M3 Surface-A directive-injection switch (default TRUE \u2014 " + "on-by-default via the plugin settings.json stamp + config.py " + "DEFAULTS). When left unset the agent keeps injecting the " + "`<active_directives>` block on every turn, byte-identical to pre-M3. " + "Set FALSE to SUPPRESS that injection for this agent \u2014 its standing " + "rules are then served from the live rules block (memory.rules_block) " + "instead of re-injected directives, which is the change that collapses " + "the always-on directive-token spend (E-41). The suppression is " + "fail-safe and re-checked every turn in recall.py: it fires ONLY when " + "a non-empty rules block is physically present in the agent's " + "CLAUDE.md \u2014 a false flag with an empty/absent block keeps injecting " + "AND emits a degraded-canary notice, so a zero-standing-rules turn is " + "unreachable. Requires memory.rules_block=true first (the block must " + "be live before injection is turned off \u2014 enforce the ordering with " + "`switchroom memory flip`, never a bare flag flip). Coordinates with " + "M4's memory.recall prefetch (memoryPrefetchEnabled): the same guard " + "gates all three recall.py emit sites so a flipped agent leaks no " + "directive on a cache/prefetch hit. Cascade: override, per-agent ONLY " + "(never fleet-seeded from the defaults/profile tier \u2014 each flip is a " + "deliberate staggered M3 rollout step, same darkness safeguard as " + "rules_block). Default-true is the fail-safe direction under the " + "Zod-strip footgun: a stripped/misread key keeps the agent injecting."),
11883
11887
  disposition: exports_external.object({
11884
11888
  skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
11885
11889
  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)."),
@@ -12325,6 +12329,7 @@ var profileFields = {
12325
12329
  profile: exports_external.string().optional(),
12326
12330
  directive_capture_nudge: exports_external.boolean().optional(),
12327
12331
  profile_capture_nudge: exports_external.boolean().optional(),
12332
+ orientation_cadence_hours: exports_external.number().int().min(1).optional(),
12328
12333
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
12329
12334
  observation_scopes: ObservationScopesSchema,
12330
12335
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.21.16", COMMIT_SHA = "d6c1267a", COMMIT_DATE = "2026-08-17T09:16:43Z";
2123
+ var VERSION = "0.21.17", COMMIT_SHA = "ac61272c", COMMIT_DATE = "2026-08-17T16:42:58Z";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -13745,6 +13745,10 @@ var init_schema = __esm(() => {
13745
13745
  }).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)."),
13746
13746
  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."),
13747
13747
  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)."),
13748
+ orientation: exports_external.boolean().optional().describe("Memory v2 M5 go-live flag (Surface B: orientation-at-boot), " + "effective default false \u2014 dark build (unset \u21d2 OFF, applied by the " + "scaffold resolver, NOT a Zod .default() \u2014 a hard default here would " + "shadow the per-agent value in the cascade the way it does every other " + "memory knob). When true, the orientation SessionStart " + "hook injects this agent's cron-refreshed `orientation` mental model " + "into context at boot AND re-seats it after every compaction (E-88), " + "deterministically and with zero tool call. Unset/false \u21d2 byte-" + "identical to pre-M5 behaviour: the hook no-ops before any bank " + "resolve or network call. Per-agent ONLY (never fleet-seeded \u2014 a " + "flip is a deliberate canary/rollout step gated on the agent's " + "m2-residue measurement + operator-created model, carve-M5 \u00a77). " + "Cascade: override (per-agent wins over default)."),
13749
+ orientation_reinject_turns: exports_external.number().int().min(0).optional().describe("Memory v2 M5 optional per-turn re-inject cadence, effective default " + "0 = off (unset \u21d2 0, applied by the scaffold resolver, not a Zod " + ".default() \u2014 same cascade-shadowing reason as `orientation`). At 0 the " + "briefing is injected only at SessionStart + compaction (the cheap " + "path). N>0 " + "would additionally re-inject the orientation briefing every N turns; " + "priced at ~55M tok/30d at every-turn, which is WHY it defaults off " + "(carve-M5 \u00a72/\u00a74). Only late-session context loss should ever set it, " + "per-agent, measured. A stripped/absent value fails to 0 (fail-safe " + "AND fail-cheap). Per-agent ONLY. Cascade: override."),
13750
+ orientation_cadence_hours: exports_external.number().int().min(1).optional().describe("Memory v2 M5 per-agent refresh cadence tier (hours), effective " + "default 48 (unset \u21d2 48, applied by the scaffold resolver, not a Zod " + ".default() \u2014 a hard default would shadow a `defaults.memory." + "orientation_cadence_hours` fleet value in the cascade). " + "The fast-churning agents (klanker, overlord) set 24; everyone else " + "inherits 48 (carve-M5 \u00a76, Ken's locked tiered cadence). Drives BOTH " + "the out-of-session refresh scheduler AND the staleness guard's " + "per-tier thresholds (stale prefix at 1.5\u00d7 cadence, cold degrade at " + "3\u00d7) \u2014 so a fixed 36h can never mislabel a 48h-tier agent as stale a " + "day early. Modelled as an explicit int (not a hard-coded name list) " + "so the tiering is data-driven and auditable. A cost knob, not an " + "enablement knob: a stripped/absent value failing to 48 is safe " + "(the cheaper tier). Cascade: override (per-agent wins over default); " + "unlike `orientation`/`_reinject_turns` it IS accepted at the " + "defaults/profile tier."),
13751
+ inject_directives: exports_external.boolean().optional().describe("Memory v2 M3 Surface-A directive-injection switch (default TRUE \u2014 " + "on-by-default via the plugin settings.json stamp + config.py " + "DEFAULTS). When left unset the agent keeps injecting the " + "`<active_directives>` block on every turn, byte-identical to pre-M3. " + "Set FALSE to SUPPRESS that injection for this agent \u2014 its standing " + "rules are then served from the live rules block (memory.rules_block) " + "instead of re-injected directives, which is the change that collapses " + "the always-on directive-token spend (E-41). The suppression is " + "fail-safe and re-checked every turn in recall.py: it fires ONLY when " + "a non-empty rules block is physically present in the agent's " + "CLAUDE.md \u2014 a false flag with an empty/absent block keeps injecting " + "AND emits a degraded-canary notice, so a zero-standing-rules turn is " + "unreachable. Requires memory.rules_block=true first (the block must " + "be live before injection is turned off \u2014 enforce the ordering with " + "`switchroom memory flip`, never a bare flag flip). Coordinates with " + "M4's memory.recall prefetch (memoryPrefetchEnabled): the same guard " + "gates all three recall.py emit sites so a flipped agent leaks no " + "directive on a cache/prefetch hit. Cascade: override, per-agent ONLY " + "(never fleet-seeded from the defaults/profile tier \u2014 each flip is a " + "deliberate staggered M3 rollout step, same darkness safeguard as " + "rules_block). Default-true is the fail-safe direction under the " + "Zod-strip footgun: a stripped/misread key keeps the agent injecting."),
13748
13752
  disposition: exports_external.object({
13749
13753
  skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
13750
13754
  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)."),
@@ -14190,6 +14194,7 @@ var init_schema = __esm(() => {
14190
14194
  profile: exports_external.string().optional(),
14191
14195
  directive_capture_nudge: exports_external.boolean().optional(),
14192
14196
  profile_capture_nudge: exports_external.boolean().optional(),
14197
+ orientation_cadence_hours: exports_external.number().int().min(1).optional(),
14193
14198
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
14194
14199
  observation_scopes: ObservationScopesSchema,
14195
14200
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -19562,6 +19567,9 @@ function generateHindsightMcpConfig(collection, memoryConfig, opts = {}) {
19562
19567
  }
19563
19568
  };
19564
19569
  }
19570
+ const grantedBanks = [
19571
+ ...new Set((opts.knowledgeExtraBanks ?? []).filter((b) => b && b !== collection))
19572
+ ];
19565
19573
  return {
19566
19574
  type: "stdio",
19567
19575
  command: HINDSIGHT_SHIM_CLI_PATH,
@@ -19569,6 +19577,7 @@ function generateHindsightMcpConfig(collection, memoryConfig, opts = {}) {
19569
19577
  env: {
19570
19578
  HINDSIGHT_MCP_URL: url,
19571
19579
  HINDSIGHT_BANK_ID: collection,
19580
+ ...grantedBanks.length > 0 ? { HINDSIGHT_KNOWLEDGE_EXTRA_BANKS: grantedBanks.join(",") } : {},
19572
19581
  HINDSIGHT_SHIM_CACHE_DIR: `${HINDSIGHT_SHIM_AGENT_HOME}/.hindsight-shim`,
19573
19582
  HOME: HINDSIGHT_SHIM_AGENT_HOME,
19574
19583
  ...opts.reflectBudget ? { HINDSIGHT_SHIM_REFLECT_BUDGET: opts.reflectBudget } : {},
@@ -28007,9 +28016,11 @@ function getHindsightSettingsEntry(agentName, config) {
28007
28016
  }
28008
28017
  const collection = getCollectionForAgent(agentName, config);
28009
28018
  const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[agentName] ?? {});
28019
+ const { additionalBanks } = resolveUsers(config, agentName);
28010
28020
  const mcpConfig = generateHindsightMcpConfig(collection, memoryConfig, {
28011
28021
  reflectBudget: resolved.memory?.reflect_budget,
28012
- reflectMaxTokens: resolved.memory?.reflect_max_tokens
28022
+ reflectMaxTokens: resolved.memory?.reflect_max_tokens,
28023
+ knowledgeExtraBanks: additionalBanks
28013
28024
  });
28014
28025
  return { key: "hindsight", value: { ...mcpConfig, alwaysLoad: false } };
28015
28026
  }
@@ -28102,6 +28113,7 @@ function getBuiltinDefaultSkillEntries() {
28102
28113
  var GOOGLE_WORKSPACE_MCP_PINNED_SHA = "db6212992738d4380a94fb6940140bcebf5d861d", MICROSOFT_WORKSPACE_MCP_PINNED_VERSION = "0.113.0", MICROSOFT_WORKSPACE_MCP_PACKAGE = "@softeria/ms-365-mcp-server", NOTION_MCP_PINNED_VERSION = "1.8.1", NOTION_MCP_PACKAGE = "@notionhq/notion-mcp-server", NOTION_TOKEN_ENV = "NOTION_TOKEN";
28103
28114
  var init_scaffold_integration = __esm(() => {
28104
28115
  init_merge();
28116
+ init_users();
28105
28117
  init_hindsight2();
28106
28118
  });
28107
28119
 
@@ -30493,6 +30505,7 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedA
30493
30505
  const additionalBanks = resolveUsers(switchroomConfig, agentName).additionalBanks;
30494
30506
  const retainConfig = resolveHindsightRetainConfig(switchroomConfig, agentName, resolvedAgentConfig);
30495
30507
  const observationScopeSettings = resolveHindsightObservationScopeSettings(switchroomConfig, agentName, resolvedAgentConfig);
30508
+ const orientationSettings = resolveHindsightOrientationSettings(switchroomConfig, agentName, resolvedAgentConfig);
30496
30509
  const recallConfig = resolveHindsightRecallConfig(switchroomConfig, agentName, resolvedAgentConfig);
30497
30510
  const recallTunables = resolveHindsightRecallTunables(recallConfig);
30498
30511
  const recallCaps = resolveHindsightRecallCaps(recallConfig);
@@ -30500,7 +30513,7 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedA
30500
30513
  try {
30501
30514
  const vendorSettingsPath = join15(sourcePath, "settings.json");
30502
30515
  if (existsSync20(vendorSettingsPath)) {
30503
- const expectedSettings = renderHindsightSettingsOverrides(readFileSync17(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings);
30516
+ const expectedSettings = renderHindsightSettingsOverrides(readFileSync17(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings);
30504
30517
  if (expectedSettings != null)
30505
30518
  contentOverrides["settings.json"] = expectedSettings;
30506
30519
  }
@@ -30518,7 +30531,7 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedA
30518
30531
  }
30519
30532
  copyDirRecursive2(sourcePath, destPath);
30520
30533
  }
30521
- applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings);
30534
+ applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings);
30522
30535
  applyHindsightHooksOverrides(destPath, recallTunables);
30523
30536
  const bankId = agentMemory?.collection ?? agentName;
30524
30537
  const mcpUrl = memory.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
@@ -30547,6 +30560,15 @@ function resolveHindsightObservationScopeSettings(switchroomConfig, agentName, r
30547
30560
  observationScopeStrategy: memory?.observation_scope_strategy
30548
30561
  };
30549
30562
  }
30563
+ function resolveHindsightOrientationSettings(switchroomConfig, agentName, resolvedAgentConfig) {
30564
+ const memory = resolvedAgentConfig ? resolvedAgentConfig.memory : switchroomConfig ? resolveAgentConfig(switchroomConfig.defaults, switchroomConfig.profiles, switchroomConfig.agents[agentName] ?? {})?.memory : undefined;
30565
+ return {
30566
+ enabled: memory?.orientation === true,
30567
+ model: "orientation",
30568
+ cadenceHours: typeof memory?.orientation_cadence_hours === "number" ? memory.orientation_cadence_hours : 48,
30569
+ reinjectTurns: typeof memory?.orientation_reinject_turns === "number" ? memory.orientation_reinject_turns : 0
30570
+ };
30571
+ }
30550
30572
  function resolveHindsightRecallConfig(switchroomConfig, agentName, resolvedAgentConfig) {
30551
30573
  if (resolvedAgentConfig) {
30552
30574
  return resolvedAgentConfig.memory?.recall;
@@ -30581,7 +30603,7 @@ function defaultRecallOwnBankMinSlots(maxMemories) {
30581
30603
  function defaultRecallAdditionalBankMinSlots(maxMemories) {
30582
30604
  return recallSlotFloorFromCap(maxMemories, RECALL_ADDITIONAL_BANK_SLOT_DIVISOR);
30583
30605
  }
30584
- function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings) {
30606
+ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings) {
30585
30607
  const settingsPath = join15(pluginDestPath, "settings.json");
30586
30608
  if (!existsSync20(settingsPath))
30587
30609
  return;
@@ -30591,12 +30613,12 @@ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retain
30591
30613
  } catch {
30592
30614
  return;
30593
30615
  }
30594
- const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings);
30616
+ const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings);
30595
30617
  if (next == null)
30596
30618
  return;
30597
30619
  writeFileSyncIfChanged(settingsPath, next);
30598
30620
  }
30599
- function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings) {
30621
+ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings) {
30600
30622
  let settings;
30601
30623
  try {
30602
30624
  settings = JSON.parse(raw);
@@ -30618,6 +30640,7 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, re
30618
30640
  settings.recallSkipTrivial = true;
30619
30641
  settings.directiveCaptureNudge = true;
30620
30642
  settings.profileCaptureNudge = true;
30643
+ settings.injectDirectives = true;
30621
30644
  settings.recallTagWeights = { ...HINDSIGHT_RECALL_TAG_WEIGHT_SEED };
30622
30645
  if (additionalBanks.length > 0) {
30623
30646
  settings.recallAdditionalBanks = [...additionalBanks];
@@ -30630,6 +30653,10 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, re
30630
30653
  if (typeof observationScopeStrategy === "string" && observationScopeStrategy.length > 0) {
30631
30654
  settings.observationScopeStrategy = observationScopeStrategy;
30632
30655
  }
30656
+ settings.memoryOrientationEnabled = orientationSettings.enabled;
30657
+ settings.memoryOrientationModel = orientationSettings.model;
30658
+ settings.memoryOrientationCadenceHours = orientationSettings.cadenceHours;
30659
+ settings.memoryOrientationReinjectTurns = orientationSettings.reinjectTurns;
30633
30660
  return JSON.stringify(settings, null, 2) + `
30634
30661
  `;
30635
30662
  }
@@ -30672,6 +30699,7 @@ function buildWorkspaceContext(args) {
30672
30699
  hindsightRecallSkipTrivial,
30673
30700
  hindsightDirectiveCaptureNudge,
30674
30701
  hindsightProfileCaptureNudge,
30702
+ hindsightInjectDirectives,
30675
30703
  hindsightObservationScopes,
30676
30704
  hindsightObservationScopeStrategy,
30677
30705
  hindsightTopicAliasesJson,
@@ -30728,6 +30756,7 @@ function buildWorkspaceContext(args) {
30728
30756
  hindsightRecallSkipTrivial,
30729
30757
  hindsightDirectiveCaptureNudge,
30730
30758
  hindsightProfileCaptureNudge,
30759
+ hindsightInjectDirectives,
30731
30760
  hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
30732
30761
  hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
30733
30762
  hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
@@ -31004,6 +31033,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
31004
31033
  const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
31005
31034
  const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
31006
31035
  const hindsightProfileCaptureNudge = agentConfig.memory?.profile_capture_nudge === undefined ? undefined : String(agentConfig.memory.profile_capture_nudge);
31036
+ const hindsightInjectDirectives = agentConfig.memory?.inject_directives === undefined ? undefined : String(agentConfig.memory.inject_directives);
31007
31037
  const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
31008
31038
  const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
31009
31039
  const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
@@ -31042,6 +31072,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
31042
31072
  hindsightRecallSkipTrivial,
31043
31073
  hindsightDirectiveCaptureNudge,
31044
31074
  hindsightProfileCaptureNudge,
31075
+ hindsightInjectDirectives,
31045
31076
  hindsightObservationScopes,
31046
31077
  hindsightObservationScopeStrategy,
31047
31078
  hindsightTopicAliasesJson,
@@ -32031,6 +32062,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
32031
32062
  const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
32032
32063
  const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
32033
32064
  const hindsightProfileCaptureNudge = agentConfig.memory?.profile_capture_nudge === undefined ? undefined : String(agentConfig.memory.profile_capture_nudge);
32065
+ const hindsightInjectDirectives = agentConfig.memory?.inject_directives === undefined ? undefined : String(agentConfig.memory.inject_directives);
32034
32066
  const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
32035
32067
  const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
32036
32068
  const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
@@ -32081,6 +32113,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
32081
32113
  hindsightRecallSkipTrivial,
32082
32114
  hindsightDirectiveCaptureNudge,
32083
32115
  hindsightProfileCaptureNudge,
32116
+ hindsightInjectDirectives,
32084
32117
  hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
32085
32118
  hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
32086
32119
  hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
@@ -32519,6 +32552,7 @@ ${body}
32519
32552
  hindsightRecallSkipTrivial,
32520
32553
  hindsightDirectiveCaptureNudge,
32521
32554
  hindsightProfileCaptureNudge,
32555
+ hindsightInjectDirectives,
32522
32556
  hindsightObservationScopes,
32523
32557
  hindsightObservationScopeStrategy,
32524
32558
  hindsightTopicAliasesJson,
@@ -66507,6 +66541,51 @@ function readClaudeMdSafe(agentsDir, agentName, readFileFn) {
66507
66541
  return null;
66508
66542
  }
66509
66543
  }
66544
+ function runDirectiveFlipChecks(config, deps = {}) {
66545
+ const readFileFn = deps.readFileFn ?? ((p) => readFileSync65(p, "utf-8"));
66546
+ let agentsDir = deps.agentsDir;
66547
+ if (agentsDir === undefined) {
66548
+ try {
66549
+ agentsDir = resolveAgentsDir(config);
66550
+ } catch {
66551
+ agentsDir = undefined;
66552
+ }
66553
+ }
66554
+ const results = [];
66555
+ for (const [agentName, agentConfig] of Object.entries(config.agents)) {
66556
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
66557
+ if (resolved.memory?.inject_directives !== false)
66558
+ continue;
66559
+ const name = `agent ${agentName} directive flip (inject_directives=false)`;
66560
+ if (resolved.memory?.rules_block !== true) {
66561
+ results.push({
66562
+ name,
66563
+ status: "fail",
66564
+ detail: "inject_directives is false but memory.rules_block is not true \u2014 the " + "rules block that must carry the migrated directives is not even " + "enabled. recall.py fails safe (keeps injecting), but fix the config: " + "enable rules_block and migrate, or re-enable inject_directives.",
66565
+ fix: `Set memory.rules_block: true for ${agentName} (and migrate its directives) before flipping.`
66566
+ });
66567
+ continue;
66568
+ }
66569
+ const claudeMd = readClaudeMdSafe(agentsDir, agentName, readFileFn);
66570
+ const parsed = claudeMd ? parseRulesBlock(claudeMd) : null;
66571
+ const ruleCount = parsed?.rules.length ?? 0;
66572
+ if (ruleCount === 0) {
66573
+ results.push({
66574
+ name,
66575
+ status: "fail",
66576
+ detail: claudeMd === null ? "inject_directives is false but the agent's CLAUDE.md could not be " + "read to confirm a rules block carries the migrated directives." : "inject_directives is false but the CLAUDE.md rules block is empty " + "(no `- **R-\u2026` rule lines). Every directive would be suppressed with " + "nothing carrying its guarantee. recall.py fails safe at runtime, but " + "fix the config: migrate the directives into the rules block, or " + "re-enable inject_directives.",
66577
+ fix: `Migrate ${agentName}'s directives into the rules block (memory rule add) or re-enable inject_directives.`
66578
+ });
66579
+ } else {
66580
+ results.push({
66581
+ name,
66582
+ status: "ok",
66583
+ detail: `flipped, and the rules block carries ${ruleCount} rule(s) \u2014 directive injection safely suppressed.`
66584
+ });
66585
+ }
66586
+ }
66587
+ return results;
66588
+ }
66510
66589
  async function runRulesBlockChecks(config, engineUrl, deps = {}) {
66511
66590
  const readFileFn = deps.readFileFn ?? ((p) => readFileSync65(p, "utf-8"));
66512
66591
  let agentsDir = deps.agentsDir;
@@ -68797,6 +68876,7 @@ async function checkHindsight(config) {
68797
68876
  results.push(...await checkBankIngestHealth(config, url, { includeConsolidationBacklog: true }));
68798
68877
  results.push(...await checkBankObservationsMissions(config, url));
68799
68878
  results.push(...await runRulesBlockChecks(config, url));
68879
+ results.push(...runDirectiveFlipChecks(config));
68800
68880
  results.push(...await checkBankConfigApiReachable(config, url));
68801
68881
  results.push(await checkObservationScopeSaturation(config, url));
68802
68882
  const memoryAgentsDir = resolveAgentsDir(config);
@@ -126158,6 +126238,19 @@ function clampKnowledgeSearchLimit(limit) {
126158
126238
  }
126159
126239
  return Math.min(KNOWLEDGE_SEARCH_LIMIT_MAX, Math.max(KNOWLEDGE_SEARCH_LIMIT_MIN, Math.trunc(limit)));
126160
126240
  }
126241
+
126242
+ class KnowledgeBankNotGrantedError extends Error {
126243
+ requested;
126244
+ ownBankId;
126245
+ grantedBanks;
126246
+ constructor(requested, ownBankId, grantedBanks) {
126247
+ super(grantedBanks.length > 0 ? `bank_id '${requested}' is not a bank you can read. You may read your ` + `own bank ('${ownBankId}') or an operator-granted bank ` + `(${grantedBanks.map((b) => `'${b}'`).join(", ")}); '${requested}' ` + `is none of these. Omit bank_id to read your own memory bank.` : `bank_id '${requested}' is not your own bank ('${ownBankId}'), and ` + `this agent has not been granted any other bank to read. Omit ` + `bank_id to read your own memory bank.`);
126248
+ this.requested = requested;
126249
+ this.ownBankId = ownBankId;
126250
+ this.grantedBanks = grantedBanks;
126251
+ this.name = "KnowledgeBankNotGrantedError";
126252
+ }
126253
+ }
126161
126254
  var KNOWLEDGE_ADMIN_TIMEOUT_MS = 15000;
126162
126255
  var KNOWLEDGE_PAGE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
126163
126256
 
@@ -126169,11 +126262,22 @@ class KnowledgeAdmin {
126169
126262
  get fetchImpl() {
126170
126263
  return this.opts.fetchImpl ?? fetch;
126171
126264
  }
126172
- knowledgeBasePath() {
126265
+ resolveBank(requested) {
126266
+ const own = this.opts.bankId;
126267
+ if (requested === undefined || requested === "" || requested === own) {
126268
+ return own;
126269
+ }
126270
+ if ((this.opts.extraBanks ?? []).includes(requested))
126271
+ return requested;
126272
+ throw new KnowledgeBankNotGrantedError(requested, own, [
126273
+ ...this.opts.extraBanks ?? []
126274
+ ]);
126275
+ }
126276
+ knowledgeBasePath(bankId) {
126173
126277
  const base = this.opts.apiBaseUrl.replace(/\/+$/, "");
126174
- return `${base}/v1/default/banks/${encodeURIComponent(this.opts.bankId)}/knowledge-base`;
126278
+ return `${base}/v1/default/banks/${encodeURIComponent(bankId)}/knowledge-base`;
126175
126279
  }
126176
- async get(url) {
126280
+ async get(url, bankId) {
126177
126281
  const ctl = new AbortController;
126178
126282
  const timer = setTimeout(() => ctl.abort(), this.opts.timeoutMs ?? KNOWLEDGE_ADMIN_TIMEOUT_MS);
126179
126283
  try {
@@ -126181,7 +126285,7 @@ class KnowledgeAdmin {
126181
126285
  method: "GET",
126182
126286
  headers: {
126183
126287
  accept: "application/json",
126184
- ...this.opts.bankId ? { "X-Bank-Id": this.opts.bankId } : {}
126288
+ ...bankId ? { "X-Bank-Id": bankId } : {}
126185
126289
  },
126186
126290
  signal: ctl.signal
126187
126291
  });
@@ -126190,13 +126294,14 @@ class KnowledgeAdmin {
126190
126294
  }
126191
126295
  }
126192
126296
  async search(args) {
126297
+ const bank = this.resolveBank(args.bankId);
126193
126298
  const params = new URLSearchParams({
126194
126299
  q: args.query,
126195
126300
  limit: String(clampKnowledgeSearchLimit(args.limit))
126196
126301
  });
126197
- const res = await this.get(`${this.knowledgeBasePath()}/search?${params}`);
126302
+ const res = await this.get(`${this.knowledgeBasePath(bank)}/search?${params}`, bank);
126198
126303
  if (!res.ok) {
126199
- throw new Error(`searching knowledge pages in bank '${this.opts.bankId}' failed: HTTP ${res.status}`);
126304
+ throw new Error(`searching knowledge pages in bank '${bank}' failed: HTTP ${res.status}`);
126200
126305
  }
126201
126306
  const parsed = await res.json();
126202
126307
  const results = Array.isArray(parsed.results) ? parsed.results : [];
@@ -126209,23 +126314,25 @@ class KnowledgeAdmin {
126209
126314
  if (!KNOWLEDGE_PAGE_ID_PATTERN.test(args.page_id)) {
126210
126315
  throw new Error(`'${args.page_id}' is not a knowledge page id. Ids look like ` + `'kp-<hex>' (letters, digits, '-' and '_' only) and come from ` + `search_knowledge_pages or get_knowledge_tree.`);
126211
126316
  }
126212
- const res = await this.get(`${this.knowledgeBasePath()}/pages/${encodeURIComponent(args.page_id)}`);
126317
+ const bank = this.resolveBank(args.bankId);
126318
+ const res = await this.get(`${this.knowledgeBasePath(bank)}/pages/${encodeURIComponent(args.page_id)}`, bank);
126213
126319
  if (res.status === 404) {
126214
- throw new Error(`no knowledge page '${args.page_id}' in bank '${this.opts.bankId}'. ` + `Page ids come from search_knowledge_pages or get_knowledge_tree.`);
126320
+ throw new Error(`no knowledge page '${args.page_id}' in bank '${bank}'. ` + `Page ids come from search_knowledge_pages or get_knowledge_tree.`);
126215
126321
  }
126216
126322
  if (!res.ok) {
126217
- throw new Error(`reading knowledge page '${args.page_id}' in bank ` + `'${this.opts.bankId}' failed: HTTP ${res.status}`);
126323
+ throw new Error(`reading knowledge page '${args.page_id}' in bank ` + `'${bank}' failed: HTTP ${res.status}`);
126218
126324
  }
126219
126325
  const page = await res.json();
126220
126326
  if (typeof page.markdown !== "string") {
126221
- throw new Error(`knowledge page '${args.page_id}' in bank '${this.opts.bankId}' came ` + `back with no markdown body \u2014 the response had no string 'markdown' ` + `field, so there is nothing to read.`);
126327
+ throw new Error(`knowledge page '${args.page_id}' in bank '${bank}' came ` + `back with no markdown body \u2014 the response had no string 'markdown' ` + `field, so there is nothing to read.`);
126222
126328
  }
126223
126329
  return page;
126224
126330
  }
126225
- async tree() {
126226
- const res = await this.get(`${this.knowledgeBasePath()}/tree`);
126331
+ async tree(args = {}) {
126332
+ const bank = this.resolveBank(args.bankId);
126333
+ const res = await this.get(`${this.knowledgeBasePath(bank)}/tree`, bank);
126227
126334
  if (!res.ok) {
126228
- throw new Error(`listing the knowledge tree in bank '${this.opts.bankId}' failed: HTTP ${res.status}`);
126335
+ throw new Error(`listing the knowledge tree in bank '${bank}' failed: HTTP ${res.status}`);
126229
126336
  }
126230
126337
  const parsed = await res.json();
126231
126338
  return { roots: Array.isArray(parsed.roots) ? parsed.roots : [] };
@@ -126278,6 +126385,10 @@ var FALLBACK_TOOL_TABLE = {
126278
126385
  update_memory: [["memory_id"], ["bank_id", "context", "entities", "fact_type", "memory_id", "occurred_end", "occurred_start", "text"]],
126279
126386
  update_mental_model: [["mental_model_id"], ["bank_id", "max_tokens", "mental_model_id", "name", "source_query", "tags", "trigger_refresh_after_consolidation"]]
126280
126387
  };
126388
+ var KNOWLEDGE_BANK_SELECTOR_PROP = {
126389
+ type: "string",
126390
+ description: "Optional. Which memory bank to read. Omit to read YOUR OWN bank (the " + "default). You may name another bank ONLY if your operator has granted " + "it to you (e.g. a shared repo knowledge bank); naming any other bank is " + "rejected."
126391
+ };
126281
126392
  var SYNTHESIZED_TOOL_TABLE = {
126282
126393
  deactivate_directive: {
126283
126394
  description: "Retire one of YOUR OWN standing directives by setting is_active=false, " + "so it stops being injected as a hard rule. Reversible with " + "reactivate_directive; the directive and its text are preserved. Pass " + "superseded_by when another directive replaces this one \u2014 both get " + "provenance tags recording the supersession. Operates only on your own " + "memory bank and changes nothing but the active flag and those tags.",
@@ -126304,7 +126415,7 @@ var SYNTHESIZED_TOOL_TABLE = {
126304
126415
  }
126305
126416
  },
126306
126417
  search_knowledge_pages: {
126307
- description: "Search YOUR OWN knowledge pages \u2014 the curated, continuously-refreshed " + "summaries of what this bank knows \u2014 with hybrid full-text + semantic " + "search. Reach for this before re-deriving a standing answer from raw " + "recall: a page is already synthesized, where recall returns fragments. " + "Returns ranked hits with a relevance snippet and a page id; read the " + "whole page with get_knowledge_page. Read-only, and only ever your own " + "memory bank. An empty knowledge base returns no results, not an error.",
126418
+ description: "Search knowledge pages \u2014 the curated, continuously-refreshed " + "summaries of what a bank knows \u2014 with hybrid full-text + semantic " + "search. Reach for this before re-deriving a standing answer from raw " + "recall: a page is already synthesized, where recall returns fragments. " + "Returns ranked hits with a relevance snippet and a page id; read the " + "whole page with get_knowledge_page. Read-only. Defaults to your own " + "bank; pass bank_id to search an operator-granted bank. An empty " + "knowledge base returns no results, not an error.",
126308
126419
  required: ["query"],
126309
126420
  props: {
126310
126421
  query: {
@@ -126316,23 +126427,27 @@ var SYNTHESIZED_TOOL_TABLE = {
126316
126427
  minimum: KNOWLEDGE_SEARCH_LIMIT_MIN,
126317
126428
  maximum: KNOWLEDGE_SEARCH_LIMIT_MAX,
126318
126429
  description: `Maximum hits to return (${KNOWLEDGE_SEARCH_LIMIT_MIN}-` + `${KNOWLEDGE_SEARCH_LIMIT_MAX}, default ${KNOWLEDGE_SEARCH_LIMIT_DEFAULT}).`
126319
- }
126430
+ },
126431
+ bank_id: KNOWLEDGE_BANK_SELECTOR_PROP
126320
126432
  }
126321
126433
  },
126322
126434
  get_knowledge_page: {
126323
- description: "Read one of YOUR OWN knowledge pages in full, by the page id returned " + "by search_knowledge_pages or get_knowledge_tree. Returns the complete " + "markdown document (YAML frontmatter + synthesized body). Prefer this " + "over rebuilding the same understanding from scratch. Read-only: it " + "returns the page as it currently stands and never triggers a refresh.",
126435
+ description: "Read one knowledge page in full, by the page id returned by " + "search_knowledge_pages or get_knowledge_tree. Returns the complete " + "markdown document (YAML frontmatter + synthesized body). Prefer this " + "over rebuilding the same understanding from scratch. Read-only: it " + "returns the page as it currently stands and never triggers a refresh. " + "Defaults to your own bank; pass bank_id (matching the tree/search call " + "the id came from) to read from an operator-granted bank.",
126324
126436
  required: ["page_id"],
126325
126437
  props: {
126326
126438
  page_id: {
126327
126439
  type: "string",
126328
126440
  description: "Id of the page to read, from search_knowledge_pages or " + "get_knowledge_tree."
126329
- }
126441
+ },
126442
+ bank_id: KNOWLEDGE_BANK_SELECTOR_PROP
126330
126443
  }
126331
126444
  },
126332
126445
  get_knowledge_tree: {
126333
- description: "List YOUR OWN knowledge base as a folder/page tree \u2014 every page's id, " + "name, source query, tags and staleness flag. Call this to see what " + "this bank already knows before reading anything else; is_stale=true " + "means the page MAY be behind newer memories, not that it is wrong. " + "Read-only, and only ever your own memory bank.",
126446
+ description: "List a knowledge base as a folder/page tree \u2014 every page's id, " + "name, source query, tags and staleness flag. Call this to see what " + "a bank already knows before reading anything else; is_stale=true " + "means the page MAY be behind newer memories, not that it is wrong. " + "Read-only. Defaults to your own bank; pass bank_id to list an " + "operator-granted bank.",
126334
126447
  required: [],
126335
- props: {}
126448
+ props: {
126449
+ bank_id: KNOWLEDGE_BANK_SELECTOR_PROP
126450
+ }
126336
126451
  }
126337
126452
  };
126338
126453
  var SYNTHESIZED_TOOL_NAMES = Object.keys(SYNTHESIZED_TOOL_TABLE);
@@ -126936,6 +127051,7 @@ class HindsightShim {
126936
127051
  this.knowledgeAdminCache = new KnowledgeAdmin({
126937
127052
  apiBaseUrl: this.opts.apiBaseUrl ?? this.opts.url.replace(/\/mcp\/?$/, ""),
126938
127053
  bankId: this.opts.bankId,
127054
+ ...this.opts.extraBanks ? { extraBanks: this.opts.extraBanks } : {},
126939
127055
  ...this.opts.fetchImpl ? { fetchImpl: this.opts.fetchImpl } : {}
126940
127056
  });
126941
127057
  }
@@ -126979,6 +127095,15 @@ class HindsightShim {
126979
127095
  if (!this.opts.bankId) {
126980
127096
  return fail4(`${name} is unavailable: this agent has no HINDSIGHT_BANK_ID, so ` + "there is no bank to pin it to.");
126981
127097
  }
127098
+ if (typeof clean3.bank_id === "string") {
127099
+ const requested = clean3.bank_id;
127100
+ const granted = requested === this.opts.bankId || (this.opts.extraBanks ?? []).includes(requested);
127101
+ if (!granted) {
127102
+ return fail4(new KnowledgeBankNotGrantedError(requested, this.opts.bankId, [
127103
+ ...this.opts.extraBanks ?? []
127104
+ ]).message);
127105
+ }
127106
+ }
126982
127107
  const preflight = await this.contractPin.preflight(name);
126983
127108
  if (!preflight.ok)
126984
127109
  return fail4(preflight.text);
@@ -126986,6 +127111,9 @@ class HindsightShim {
126986
127111
  const text = await this.runSynthesized(name, clean3);
126987
127112
  return { content: [{ type: "text", text }], isError: false };
126988
127113
  } catch (err) {
127114
+ if (err instanceof KnowledgeBankNotGrantedError) {
127115
+ return fail4(err.message);
127116
+ }
126989
127117
  if (err instanceof DirectivePairInconsistentError) {
126990
127118
  this.log(`[hindsight-shim] ${err.message}`);
126991
127119
  }
@@ -127004,23 +127132,25 @@ class HindsightShim {
127004
127132
  case "search_knowledge_pages": {
127005
127133
  const res = await this.knowledgeAdmin.search({
127006
127134
  query: args.query,
127007
- ...typeof args.limit === "number" ? { limit: args.limit } : {}
127135
+ ...typeof args.limit === "number" ? { limit: args.limit } : {},
127136
+ ...typeof args.bank_id === "string" ? { bankId: args.bank_id } : {}
127008
127137
  });
127009
127138
  if (res.results.length === 0) {
127010
- return "No knowledge pages matched that query in your memory bank. " + "The bank may have no pages yet \u2014 get_knowledge_tree lists what " + "exists.";
127139
+ return "No knowledge pages matched that query in " + (typeof args.bank_id === "string" ? `bank '${args.bank_id}'. ` : "your memory bank. ") + "The bank may have no pages yet \u2014 get_knowledge_tree lists what " + "exists.";
127011
127140
  }
127012
127141
  return capKnowledgeResponse(JSON.stringify({ total: res.total, results: res.results }, null, 2));
127013
127142
  }
127014
127143
  case "get_knowledge_page": {
127015
127144
  const page = await this.knowledgeAdmin.getPage({
127016
- page_id: args.page_id
127145
+ page_id: args.page_id,
127146
+ ...typeof args.bank_id === "string" ? { bankId: args.bank_id } : {}
127017
127147
  });
127018
127148
  return capKnowledgeResponse(page.markdown);
127019
127149
  }
127020
127150
  case "get_knowledge_tree": {
127021
- const tree = await this.knowledgeAdmin.tree();
127151
+ const tree = await this.knowledgeAdmin.tree(typeof args.bank_id === "string" ? { bankId: args.bank_id } : {});
127022
127152
  if (tree.roots.length === 0) {
127023
- return "Your knowledge base has no pages yet. Pages are synthesized from " + "mental models \u2014 propose one to start it.";
127153
+ return typeof args.bank_id === "string" ? `Bank '${args.bank_id}' has no knowledge pages yet.` : "Your knowledge base has no pages yet. Pages are synthesized " + "from mental models \u2014 propose one to start it.";
127024
127154
  }
127025
127155
  return renderKnowledgeTree(tree.roots);
127026
127156
  }
@@ -127134,9 +127264,11 @@ class HindsightShim {
127134
127264
  }
127135
127265
  function resolveShimOptionsFromEnv(env2) {
127136
127266
  const home2 = env2.HOME && env2.HOME !== "/" ? env2.HOME : tmpdir6();
127267
+ const extraBanks = (env2.HINDSIGHT_KNOWLEDGE_EXTRA_BANKS ?? "").split(",").map((b) => b.trim()).filter((b) => b.length > 0 && b !== (env2.HINDSIGHT_BANK_ID || ""));
127137
127268
  return {
127138
127269
  url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
127139
127270
  bankId: env2.HINDSIGHT_BANK_ID || "",
127271
+ ...extraBanks.length > 0 ? { extraBanks } : {},
127140
127272
  cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join117(home2, ".hindsight-shim")
127141
127273
  };
127142
127274
  }