switchroom 0.21.15 → 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.
- package/bin/rules-sentinel-hook.sh +101 -0
- package/dist/agent-scheduler/index.js +6 -0
- package/dist/auth-broker/index.js +6 -0
- package/dist/cli/notion-write-pretool.mjs +6 -0
- package/dist/cli/switchroom.js +2086 -871
- package/dist/host-control/main.js +7 -1
- package/dist/vault/approvals/kernel-server.js +6 -0
- package/dist/vault/broker/server.js +6 -0
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +11 -0
- package/profiles/_shared/delegation-golden-rule.md.hbs +2 -0
- package/skills/mental-model-curator/SKILL.md +187 -56
- package/telegram-plugin/dist/gateway/gateway.js +10 -4
- package/vendor/hindsight-memory/hooks/hooks.json +19 -0
- package/vendor/hindsight-memory/scripts/directive_verify.py +43 -1
- package/vendor/hindsight-memory/scripts/lib/client.py +35 -0
- package/vendor/hindsight-memory/scripts/lib/config.py +47 -0
- package/vendor/hindsight-memory/scripts/lib/orientation.py +248 -0
- package/vendor/hindsight-memory/scripts/lib/recall_buffer.py +265 -0
- package/vendor/hindsight-memory/scripts/orientation.py +195 -0
- package/vendor/hindsight-memory/scripts/prefetch.py +166 -0
- package/vendor/hindsight-memory/scripts/recall.py +323 -5
- package/vendor/hindsight-memory/scripts/retain.py +132 -49
- package/vendor/hindsight-memory/scripts/setup_hooks.py +10 -1
- package/vendor/hindsight-memory/scripts/tests/fixtures/rules-block.golden.md +9 -0
- package/vendor/hindsight-memory/scripts/tests/test_orientation_hook.py +283 -0
- package/vendor/hindsight-memory/scripts/tests/test_orientation_logic.py +176 -0
- package/vendor/hindsight-memory/scripts/tests/test_prefetch_invalidation.py +329 -0
- package/vendor/hindsight-memory/scripts/tests/test_prefetch_pipeline.py +247 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_buffer.py +143 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_buffer_join.py +193 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_cap_truncation.py +133 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_directive_suppression.py +328 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_junk_gate.py +168 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_no_score_floor.py +124 -0
- package/vendor/hindsight-memory/scripts/tests/test_retain_delta.py +304 -0
- package/vendor/hindsight-memory/scripts/tests/test_retain_stop_hook_prefetch_gate.py +109 -0
|
@@ -11143,6 +11143,11 @@ var init_schema = __esm(() => {
|
|
|
11143
11143
|
}
|
|
11144
11144
|
}).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry — e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced — 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded — each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
|
|
11145
11145
|
observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
|
|
11146
|
+
rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded — the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false ⇒ byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded — each agent's flip is a deliberate M3 rollout step)."),
|
|
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."),
|
|
11146
11151
|
disposition: exports_external.object({
|
|
11147
11152
|
skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
|
|
11148
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)."),
|
|
@@ -11588,6 +11593,7 @@ var init_schema = __esm(() => {
|
|
|
11588
11593
|
profile: exports_external.string().optional(),
|
|
11589
11594
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
11590
11595
|
profile_capture_nudge: exports_external.boolean().optional(),
|
|
11596
|
+
orientation_cadence_hours: exports_external.number().int().min(1).optional(),
|
|
11591
11597
|
anti_confabulation_directive: AntiConfabulationDirectiveSchema,
|
|
11592
11598
|
observation_scopes: ObservationScopesSchema,
|
|
11593
11599
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
@@ -21592,7 +21598,7 @@ function allocateAgentUid(name) {
|
|
|
21592
21598
|
}
|
|
21593
21599
|
|
|
21594
21600
|
// src/build-info.ts
|
|
21595
|
-
var VERSION = "0.21.
|
|
21601
|
+
var VERSION = "0.21.17";
|
|
21596
21602
|
|
|
21597
21603
|
// src/setup/hindsight-recall-passthrough.ts
|
|
21598
21604
|
var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
|
|
@@ -4583,6 +4583,11 @@ var init_schema = __esm(() => {
|
|
|
4583
4583
|
}
|
|
4584
4584
|
}).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry — e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced — 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded — each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
|
|
4585
4585
|
observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
|
|
4586
|
+
rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded — the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false ⇒ byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded — each agent's flip is a deliberate M3 rollout step)."),
|
|
4587
|
+
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)."),
|
|
4588
|
+
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."),
|
|
4589
|
+
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."),
|
|
4590
|
+
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."),
|
|
4586
4591
|
disposition: exports_external.object({
|
|
4587
4592
|
skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
|
|
4588
4593
|
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)."),
|
|
@@ -5028,6 +5033,7 @@ var init_schema = __esm(() => {
|
|
|
5028
5033
|
profile: exports_external.string().optional(),
|
|
5029
5034
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
5030
5035
|
profile_capture_nudge: exports_external.boolean().optional(),
|
|
5036
|
+
orientation_cadence_hours: exports_external.number().int().min(1).optional(),
|
|
5031
5037
|
anti_confabulation_directive: AntiConfabulationDirectiveSchema,
|
|
5032
5038
|
observation_scopes: ObservationScopesSchema,
|
|
5033
5039
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
@@ -4179,6 +4179,11 @@ var init_schema = __esm(() => {
|
|
|
4179
4179
|
}
|
|
4180
4180
|
}).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry — e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced — 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded — each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
|
|
4181
4181
|
observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
|
|
4182
|
+
rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded — the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false ⇒ byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded — each agent's flip is a deliberate M3 rollout step)."),
|
|
4183
|
+
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)."),
|
|
4184
|
+
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."),
|
|
4185
|
+
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."),
|
|
4186
|
+
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."),
|
|
4182
4187
|
disposition: exports_external.object({
|
|
4183
4188
|
skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
|
|
4184
4189
|
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)."),
|
|
@@ -4624,6 +4629,7 @@ var init_schema = __esm(() => {
|
|
|
4624
4629
|
profile: exports_external.string().optional(),
|
|
4625
4630
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
4626
4631
|
profile_capture_nudge: exports_external.boolean().optional(),
|
|
4632
|
+
orientation_cadence_hours: exports_external.number().int().min(1).optional(),
|
|
4627
4633
|
anti_confabulation_directive: AntiConfabulationDirectiveSchema,
|
|
4628
4634
|
observation_scopes: ObservationScopesSchema,
|
|
4629
4635
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
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.
|
|
4
|
+
"version": "0.21.17",
|
|
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": {
|
|
@@ -1198,6 +1198,17 @@ export HINDSIGHT_DIRECTIVE_CAPTURE_NUDGE={{hindsightDirectiveCaptureNudge}}
|
|
|
1198
1198
|
{{#if hindsightProfileCaptureNudge}}
|
|
1199
1199
|
export HINDSIGHT_PROFILE_CAPTURE_NUDGE={{hindsightProfileCaptureNudge}}
|
|
1200
1200
|
{{/if}}
|
|
1201
|
+
# Memory v2 M3 Surface-A — directive-injection switch (memory.inject_directives
|
|
1202
|
+
# cascade). On by default (plugin settings.json): recall.py injects the
|
|
1203
|
+
# <active_directives> block every turn. Export only when the operator overrode
|
|
1204
|
+
# it; set false to SUPPRESS injection for this agent (a flipped M3 canary) — its
|
|
1205
|
+
# standing rules then come from the live rules block instead. recall.py keeps
|
|
1206
|
+
# the suppression fail-safe: it drops injection ONLY when a non-empty rules
|
|
1207
|
+
# block is physically present in CLAUDE.md, else it keeps injecting and emits a
|
|
1208
|
+
# degraded-canary notice. Flip via `switchroom memory flip`, never a bare edit.
|
|
1209
|
+
{{#if hindsightInjectDirectives}}
|
|
1210
|
+
export HINDSIGHT_INJECT_DIRECTIVES={{hindsightInjectDirectives}}
|
|
1211
|
+
{{/if}}
|
|
1201
1212
|
# Per-row observation scope (memory.observation_scopes cascade). Stamped on
|
|
1202
1213
|
# every retain — Stop hook, sidechain, boot reconcile, queue drain, backfill.
|
|
1203
1214
|
# "shared" makes consolidation pool this agent's observations into ONE global
|
|
@@ -7,3 +7,5 @@ Acting in-turn and delegating are the same move: for an execution-class task the
|
|
|
7
7
|
If the user amends in-flight delegated work mid-turn, steer the running worker now (`SendMessage` by worker name or agent id) rather than holding it for handback — and say in your reply which you did ("folded your update into the running worker" / "queued as a separate task"); never classify silently, never inferred. Unsure? Queue it and say so — queue is the default. If the worker is effectively done and the steer lands too late, say so and apply the update yourself in the parent.
|
|
8
8
|
|
|
9
9
|
If no sub-agents are configured, do the work yourself.
|
|
10
|
+
|
|
11
|
+
A worker has no auto-recall of its own — it can only PULL its own bank read-only (`mcp__hindsight__recall` / `get_mental_model` / `get_knowledge_tree` / `get_knowledge_page` / `search_knowledge_pages`; M6/E-86). When you dispatch, put the orientation you already hold — the relevant roster, bank names, or prior findings — into the Task prompt as your deliberate context selection. Share orientation, not secrets: never paste a credential or another agent's private fact into a dispatch prompt.
|
|
@@ -9,11 +9,12 @@ description: >
|
|
|
9
9
|
"review my memory bank", "what standing models should I have", "bootstrap
|
|
10
10
|
mental models", "suggest mental models from my bank", "audit my mental models",
|
|
11
11
|
"what recurring questions do I keep re-deriving", and typos like "propose
|
|
12
|
-
knowlege models". ALSO owns the directive
|
|
13
|
-
"audit my directives", "
|
|
14
|
-
directives
|
|
15
|
-
guardrails", and when switchroom doctor
|
|
16
|
-
threshold. Do NOT use for storing a
|
|
12
|
+
knowlege models". ALSO owns the directive TRIAGE pass (Memory v2 M2) —
|
|
13
|
+
triggers like "audit my directives", "triage my directives", "merge my
|
|
14
|
+
directives", "retire stale directives", "my directives are overlapping", "too
|
|
15
|
+
many active directives", "clean up my guardrails", and when switchroom doctor
|
|
16
|
+
FLAGs a bank over the directive WARN/FAIL threshold. Do NOT use for storing a
|
|
17
|
+
single fact/preference/decision —
|
|
17
18
|
that is retain, not a mental model. Do NOT use for identity or "who is the
|
|
18
19
|
user" — that lives in profile banks, so never propose an identity model here
|
|
19
20
|
(the operator's review of the card, not any code guard, is the only gate — so
|
|
@@ -22,7 +23,7 @@ description: >
|
|
|
22
23
|
whose shape you know, PROPOSE it through the approve/deny card — never call
|
|
23
24
|
create_mental_model directly (it is not pre-approved; the propose card is the
|
|
24
25
|
only sanctioned write path, Fix 1.2 / #2903).
|
|
25
|
-
allowed-tools: mcp__hindsight__list_banks mcp__hindsight__get_bank_stats mcp__hindsight__list_mental_models mcp__hindsight__get_mental_model mcp__hindsight__list_directives mcp__hindsight__reflect mcp__hindsight__recall mcp__switchroom-telegram__mental_model_propose
|
|
26
|
+
allowed-tools: mcp__hindsight__list_banks mcp__hindsight__get_bank_stats mcp__hindsight__list_mental_models mcp__hindsight__get_mental_model mcp__hindsight__list_directives mcp__hindsight__deactivate_directive mcp__hindsight__reactivate_directive mcp__hindsight__reflect mcp__hindsight__recall mcp__switchroom-telegram__mental_model_propose Bash(switchroom memory directive mark-rules-block *)
|
|
26
27
|
---
|
|
27
28
|
|
|
28
29
|
# mental-model-curator — Propose standing knowledge models from your own bank
|
|
@@ -58,9 +59,11 @@ models. Propose the few that are earned; let the operator ratify.
|
|
|
58
59
|
(name + source_query + one-line reason) and STOP. Do NOT call
|
|
59
60
|
`mental_model_propose` in this mode. This is the safe way to run against any
|
|
60
61
|
bank.
|
|
61
|
-
- **Directive
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
- **Directive triage pass:** a separate job — see "Directive triage pass (Memory
|
|
63
|
+
v2 M2)" below. It renders one consolidated card as text, then requires an
|
|
64
|
+
explicit operator go-ahead before ANY deactivation executes — never enacts on
|
|
65
|
+
the same turn the card is shown — and runs independently of the mental-model
|
|
66
|
+
modes above.
|
|
64
67
|
|
|
65
68
|
## Workflow
|
|
66
69
|
|
|
@@ -152,23 +155,87 @@ empty topic at 3am is useless and noisy. If invoked from a scheduled sweep,
|
|
|
152
155
|
the next interactive turn instead of firing cards unattended. Treat live-turn,
|
|
153
156
|
operator-present proposing as the only supported path for now.
|
|
154
157
|
|
|
155
|
-
## Directive
|
|
156
|
-
|
|
157
|
-
A second, independent job of this skill:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
directives
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
158
|
+
## Directive triage pass (Memory v2 M2)
|
|
159
|
+
|
|
160
|
+
A second, independent job of this skill: sort the bank's **active directives**
|
|
161
|
+
into E-45's five categories and clear the four heavy banks off the WARN line.
|
|
162
|
+
Directives are hard rules applied on every `reflect` — but the bank caps active
|
|
163
|
+
directives at **`MAX_DIRECTIVES=30`**. Past the cap, the lowest-priority
|
|
164
|
+
directives are **truncated** from the `<active_directives>` recall block and
|
|
165
|
+
never reach the agent (recorded as `directives_omitted` on the recall_log row
|
|
166
|
+
— the recall hook's stderr warning is swallowed by Claude Code, so don't look
|
|
167
|
+
there). Fleet doctor WARNs at >24 active and FAILs at >30 (`doctor-memory.ts`,
|
|
168
|
+
`DIRECTIVE_WARN_THRESHOLD`), and its fix text points here.
|
|
169
|
+
|
|
170
|
+
**Honest framing (do not oversell):** as of the M2 measurement pass, NOTHING is
|
|
171
|
+
currently being silently dropped fleet-wide — zero directives have ever been
|
|
172
|
+
retired, and `directives_omitted` has fired zero times. This is a **budget +
|
|
173
|
+
hygiene** pass, not a rescue from silent drops. Say it that way; overstating the
|
|
174
|
+
urgency is its own failure.
|
|
175
|
+
|
|
176
|
+
**The five categories every active directive sorts into (E-45):**
|
|
177
|
+
|
|
178
|
+
| Category | Meaning | What happens in THIS pass |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| `reflect-directive` | A genuine compliance guardrail (§4.2's designed role) | KEEP — no change |
|
|
181
|
+
| `rules-block` | A standing preference that belongs in the CLAUDE.md rules block (M1) | **STAGE ONLY — stays ACTIVE.** See the hard rule below. |
|
|
182
|
+
| `disposition` | Better expressed as `disposition_*` bank config (E-40) | Deactivate; note the config change separately for the operator — do NOT auto-apply it |
|
|
183
|
+
| `retire` | Superseded or mechanized — dead weight | Deactivate (record `superseded_by` when a winner exists) |
|
|
184
|
+
| `retain-as-memory` | A fact filed as a directive by mistake (category error) | Deactivate — verify the fact already exists as a memory in the bank BEFORE proposing this; don't assert it, check it |
|
|
185
|
+
|
|
186
|
+
**Retirement signals are deterministic and bounded (E-45).** A directive is
|
|
187
|
+
ONLY a retirement candidate when you can point to one of: (a) it is tagged
|
|
188
|
+
`superseded-by:<name>` (or you would tag it that way, naming the winner), or
|
|
189
|
+
(b) a mechanization reference (a named commit/PR/scaffold rule that now does
|
|
190
|
+
this automatically), or (c) a documented category error you can verify against
|
|
191
|
+
the bank's own memories/config. **Citation counts are NEVER a signal** (a rule
|
|
192
|
+
that works quietly gets zero citations). **Age alone is NEVER sufficient.** A
|
|
193
|
+
directive with none of these signals is `reflect-directive` — KEEP — by
|
|
194
|
+
default. Never propose retiring a directive because it "seems old" or "looks
|
|
195
|
+
unused."
|
|
196
|
+
|
|
197
|
+
### HARD RULE — never deactivate a `rules-block`-category directive in this pass
|
|
198
|
+
|
|
199
|
+
If a directive's behaviour is destined for the CLAUDE.md rules block (M1), its
|
|
200
|
+
migration happens at **M3 flip time**, when that surface goes live for this
|
|
201
|
+
agent — not now. M1 shipped dark (`memory.rules_block` off on every agent
|
|
202
|
+
today); deactivating a `rules-block`-category directive before that flip would
|
|
203
|
+
leave the agent with **neither** the directive (deactivated) **nor** the rule
|
|
204
|
+
(block not live) — a silent guardrail gap for however long the M2→M3 interval
|
|
205
|
+
runs. So: **for every directive you categorise `rules-block`, call
|
|
206
|
+
`list_directives` to confirm it, but do NOT call `deactivate_directive` on it
|
|
207
|
+
in this pass, ever** — only stage its text (mention it in the card as staged,
|
|
208
|
+
count it, and leave it exactly as-is) for the M3 flip to pick up later
|
|
209
|
+
(`rule add`, once `memory.rules_block` is on for this agent).
|
|
210
|
+
|
|
211
|
+
**This is not just a prose rule you have to remember — it is now also code-
|
|
212
|
+
enforced, but ONLY once you complete the mandatory step below.**
|
|
213
|
+
`DirectiveAdmin` (`src/memory/hindsight-directive-admin.ts` —
|
|
214
|
+
`deactivate`/`deactivateById`/`deactivateByIdWithTag`) refuses to deactivate
|
|
215
|
+
ANY directive carrying the persisted `triage-category:rules-block` marker
|
|
216
|
+
tag, on every call path, including this skill's own `deactivate_directive`
|
|
217
|
+
calls. But that refusal only ever fires for a directive that actually
|
|
218
|
+
CARRIES the tag — and nothing stamps the tag automatically just because you,
|
|
219
|
+
in your own reasoning, decided a directive is `rules-block`. **You must
|
|
220
|
+
stamp it yourself, immediately, as part of classification — not as an
|
|
221
|
+
afterthought before presenting the card:**
|
|
222
|
+
|
|
223
|
+
> For every directive you classify `rules-block` in step 2 below, before you
|
|
224
|
+
> move on to the next directive, run:
|
|
225
|
+
> ```
|
|
226
|
+
> switchroom memory directive mark-rules-block <agent> <id>
|
|
227
|
+
> ```
|
|
228
|
+
> (`src/cli/memory-directive.ts` — this calls the exact same
|
|
229
|
+
> `DirectiveAdmin.markRulesBlock` write the batch triage executor uses, so
|
|
230
|
+
> the chokepoint above keys off it identically). Idempotent — re-running it
|
|
231
|
+
> on an already-marked directive is a no-op, so it is always safe to call
|
|
232
|
+
> even if you are not sure whether a prior pass already marked this one.
|
|
233
|
+
|
|
234
|
+
Do this for EVERY rules-block row before step 3's card renders, not just the
|
|
235
|
+
ones you personally plan to leave alone — the whole point is that the code
|
|
236
|
+
guard, not your own discipline in this pass or a future one, is what refuses
|
|
237
|
+
a later deactivation attempt (including a mistaken one, or one made by a
|
|
238
|
+
different pass that never saw this reasoning).
|
|
172
239
|
|
|
173
240
|
### When to run it
|
|
174
241
|
|
|
@@ -178,40 +245,104 @@ outputs a plan; it does not enact retirements itself.
|
|
|
178
245
|
|
|
179
246
|
### Workflow
|
|
180
247
|
|
|
181
|
-
1. **List
|
|
182
|
-
|
|
248
|
+
1. **List every directive, active and inactive context.** `mcp__hindsight__list_directives`
|
|
249
|
+
(active only is fine for triage — inactive ones are already off). If the
|
|
250
|
+
active count is comfortably under the WARN threshold (≤24) AND nothing reads
|
|
183
251
|
stale/overlapping, STOP and report "directive set is healthy (N active) —
|
|
184
|
-
nothing to
|
|
185
|
-
2. **
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
- **
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
252
|
+
nothing to triage." Don't manufacture churn.
|
|
253
|
+
2. **Classify every directive — no drops.** For each one, assign exactly one of
|
|
254
|
+
the five categories above, with the deterministic signal that justifies it
|
|
255
|
+
(or "no signal — KEEP" for the default case). Do this for the FULL set —
|
|
256
|
+
the card is worthless if a directive silently doesn't appear on it.
|
|
257
|
+
**The moment you classify a directive `rules-block`, run
|
|
258
|
+
`switchroom memory directive mark-rules-block <agent> <id>` for it right
|
|
259
|
+
then** (see the HARD RULE above) — do not defer this to a later step or
|
|
260
|
+
batch it at the end; every rules-block row must be stamped before you move
|
|
261
|
+
to step 3.
|
|
262
|
+
3. **Render ONE consolidated card as text.** Two sections, KEEP/staged first,
|
|
263
|
+
retirement candidates second — never interleaved, so a skim of "the bottom
|
|
264
|
+
half" cannot land on a live guardrail:
|
|
265
|
+
- **Keep / staged (N):** every `reflect-directive` and `rules-block` row,
|
|
266
|
+
each showing priority + category + signal (or "no signal — default KEEP").
|
|
267
|
+
- **Retirement candidates (N):** every `disposition` / `retire` /
|
|
268
|
+
`retain-as-memory` row, each showing priority + category + the
|
|
269
|
+
deterministic signal — never a bare category with no rationale.
|
|
270
|
+
- The resulting active count vs `MAX_DIRECTIVES` if the retirements land.
|
|
271
|
+
4. **STOP. Get an explicit operator go-ahead before touching anything.** Do not
|
|
272
|
+
deactivate on the same turn the card is shown. The card is the operator's
|
|
273
|
+
one look before a batch of guardrails changes state.
|
|
274
|
+
5. **On go-ahead, retire ONE AT A TIME with a human tap PER retirement — never
|
|
275
|
+
a single blanket go-ahead for the whole batch.** `deactivate_directive` is
|
|
276
|
+
pre-approved fleet-wide (`scaffold.ts`'s 2026-07-29 amendment,
|
|
277
|
+
`HINDSIGHT_MCP_TOOLS`) — unlike `delete_directive`, calling it does NOT
|
|
278
|
+
raise a Telegram permission card by itself. That is exactly why this skill
|
|
279
|
+
must supply the per-call gate `delete_directive`'s card would otherwise
|
|
280
|
+
have given: present the NEXT retirement candidate (name, priority, signal,
|
|
281
|
+
proposed `superseded_by`) on its own, then **STOP and end the turn** —
|
|
282
|
+
do not proceed to it, and do not queue the next one, until the operator's
|
|
283
|
+
reply for THIS ONE arrives. Only after an explicit per-row confirmation do
|
|
284
|
+
you call `mcp__hindsight__deactivate_directive` for that single directive.
|
|
285
|
+
Then present the next candidate the same way. A single "yes, go ahead" that
|
|
286
|
+
covers the whole retirement list is NOT sufficient — that collapses back
|
|
287
|
+
to the batch-level prose go-ahead this per-row loop exists to replace, and
|
|
288
|
+
silently trades away the human tap `delete_directive`'s approval card used
|
|
289
|
+
to guarantee for every one of these retirements (see PR #4760 review B2).
|
|
290
|
+
Pass `superseded_by` when you named a winner. After the LAST retirement in
|
|
291
|
+
the plan lands, `list_directives` again and report the new active count
|
|
292
|
+
against `MAX_DIRECTIVES`, plus which directives moved where.
|
|
293
|
+
6. **Disposition rows are a note, not an auto-edit.** When you deactivate a
|
|
294
|
+
`disposition`-category directive, separately flag to the operator which
|
|
295
|
+
`disposition_*` bank config change it corresponds to — do NOT edit bank
|
|
296
|
+
config yourself in this pass (E-40: audit before leaning on it).
|
|
297
|
+
|
|
298
|
+
### windows-boxes-class fixes (a drifted directive with a peer's superset text) — special-cased, rare
|
|
299
|
+
|
|
300
|
+
Do NOT attempt this as an ordinary retirement. If a directive on this bank has
|
|
301
|
+
drifted from a peer agent's superset/reconciled version of the "same" rule
|
|
302
|
+
(content differs but the two should say the same thing), that is NOT
|
|
303
|
+
expressible as a `content`-PATCH — the shim deliberately refuses to PATCH
|
|
304
|
+
directive `content` (`hindsight-directive-admin.ts` — a directive body is the
|
|
305
|
+
guardrail itself; there is no history table to recover a bad rewrite from).
|
|
306
|
+
It is also NOT safely expressible as plain `create_directive` +
|
|
307
|
+
`deactivate_directive` calls from this skill: once the new copy is created
|
|
308
|
+
carrying the SAME name, `deactivate_directive`'s name-resolution sees two
|
|
309
|
+
directives sharing that name and refuses as ambiguous. Use
|
|
310
|
+
`reconcileDirectiveSuperset` (`src/memory/directive-triage-executor.ts`) for
|
|
311
|
+
this instead — it resolves the old copy's id BEFORE creating the new one and
|
|
312
|
+
retires it by id, so the name collision never becomes an ambiguity error. This
|
|
313
|
+
is NOT a bare MCP tool call from this skill (no MCP tool exposes an
|
|
314
|
+
id-targeted deactivate) — it runs via
|
|
315
|
+
`switchroom memory directive reconcile <agent> <name> <content...>`
|
|
316
|
+
(`src/cli/memory-directive.ts`), an operator or repo-authorized CLI/script
|
|
317
|
+
invocation, not something this skill invokes autonomously. Flag any drift you
|
|
318
|
+
notice to the operator rather than improvising a fix.
|
|
204
319
|
|
|
205
320
|
### Directive-pass anti-patterns
|
|
206
321
|
|
|
207
|
-
- ❌
|
|
208
|
-
|
|
209
|
-
- ❌
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
322
|
+
- ❌ Deactivating a `rules-block`-category directive before M3 flips this
|
|
323
|
+
agent — opens a guardrail gap; see the hard rule above.
|
|
324
|
+
- ❌ Classifying a directive `rules-block` without immediately calling
|
|
325
|
+
`switchroom memory directive mark-rules-block <agent> <id>` for it — an
|
|
326
|
+
unmarked directive gets NO code-level protection from the
|
|
327
|
+
`DirectiveAdmin` chokepoint; the classification in your head is not
|
|
328
|
+
enough.
|
|
329
|
+
- ❌ Retiring on age or citation count alone — E-45 refuses both as signals.
|
|
330
|
+
- ❌ Calling `deactivate_directive` in parallel / without awaiting each one —
|
|
331
|
+
sequential only (tag-write race).
|
|
332
|
+
- ❌ Executing on the same turn the card is shown — the card review is the
|
|
333
|
+
gate; always stop for an explicit go-ahead first.
|
|
334
|
+
- ❌ Treating one blanket "yes, go ahead" as clearance for the whole
|
|
335
|
+
retirement list — `deactivate_directive` is pre-approved (no Telegram card
|
|
336
|
+
per call), so this skill's own per-retirement stop-and-confirm loop IS the
|
|
337
|
+
human tap; batching it away silently weakens the guardrail (review B2).
|
|
338
|
+
- ❌ Auto-editing bank config for a `disposition` row — flag it, don't apply it.
|
|
339
|
+
- ❌ Treating a drifted/superset-text directive as an ordinary retirement —
|
|
340
|
+
see the windows-boxes-class section above.
|
|
341
|
+
- ❌ Manufacturing retirements on a healthy small set just to "tidy" — run the
|
|
342
|
+
pass when it's earned (bloat, overlap, staleness, or a doctor flag), not
|
|
343
|
+
reflexively.
|
|
344
|
+
- ❌ Leaving a live contradictory pair in place — surface it as the top
|
|
345
|
+
retirement candidate; it's what makes `reflect` nondeterministic.
|
|
215
346
|
|
|
216
347
|
## Anti-patterns
|
|
217
348
|
|
|
@@ -21797,6 +21797,11 @@ var init_schema = __esm(() => {
|
|
|
21797
21797
|
}
|
|
21798
21798
|
}).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry \u2014 e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced \u2014 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded \u2014 each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
|
|
21799
21799
|
observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
|
|
21800
|
+
rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded \u2014 the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false \u21d2 byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded \u2014 each agent's flip is a deliberate M3 rollout step)."),
|
|
21801
|
+
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)."),
|
|
21802
|
+
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."),
|
|
21803
|
+
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."),
|
|
21804
|
+
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."),
|
|
21800
21805
|
disposition: exports_external.object({
|
|
21801
21806
|
skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
|
|
21802
21807
|
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)."),
|
|
@@ -22242,6 +22247,7 @@ var init_schema = __esm(() => {
|
|
|
22242
22247
|
profile: exports_external.string().optional(),
|
|
22243
22248
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
22244
22249
|
profile_capture_nudge: exports_external.boolean().optional(),
|
|
22250
|
+
orientation_cadence_hours: exports_external.number().int().min(1).optional(),
|
|
22245
22251
|
anti_confabulation_directive: AntiConfabulationDirectiveSchema,
|
|
22246
22252
|
observation_scopes: ObservationScopesSchema,
|
|
22247
22253
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
@@ -105870,10 +105876,10 @@ function startOutboxSweep(deps) {
|
|
|
105870
105876
|
}
|
|
105871
105877
|
|
|
105872
105878
|
// ../src/build-info.ts
|
|
105873
|
-
var VERSION2 = "0.21.
|
|
105874
|
-
var COMMIT_SHA = "
|
|
105875
|
-
var COMMIT_DATE = "2026-08-
|
|
105876
|
-
var LATEST_PR =
|
|
105879
|
+
var VERSION2 = "0.21.17";
|
|
105880
|
+
var COMMIT_SHA = "ac61272c";
|
|
105881
|
+
var COMMIT_DATE = "2026-08-17T16:42:58Z";
|
|
105882
|
+
var LATEST_PR = 4768;
|
|
105877
105883
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
105878
105884
|
|
|
105879
105885
|
// gateway/boot-version.ts
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
"timeout": 30
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "python3 \"${CLAUDE_PLUGIN_ROOT}/scripts/orientation.py\"",
|
|
19
|
+
"timeout": 8
|
|
20
|
+
}
|
|
21
|
+
]
|
|
13
22
|
}
|
|
14
23
|
],
|
|
15
24
|
"UserPromptSubmit": [
|
|
@@ -42,6 +51,16 @@
|
|
|
42
51
|
"async": true
|
|
43
52
|
}
|
|
44
53
|
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "python3 \"${CLAUDE_PLUGIN_ROOT}/scripts/prefetch.py\"",
|
|
60
|
+
"timeout": 20,
|
|
61
|
+
"async": true
|
|
62
|
+
}
|
|
63
|
+
]
|
|
45
64
|
}
|
|
46
65
|
],
|
|
47
66
|
"SubagentStop": [
|
|
@@ -64,6 +64,7 @@ from lib.directives import ( # noqa: E402
|
|
|
64
64
|
parse_active_directives_block,
|
|
65
65
|
rule_already_captured,
|
|
66
66
|
)
|
|
67
|
+
from lib import recall_buffer # noqa: E402
|
|
67
68
|
|
|
68
69
|
# Reuse Stage B's pleasantry scrub so "as always" / "always happy to help"
|
|
69
70
|
# can't trip the high-confidence detector either. Imported lazily-safe: if
|
|
@@ -387,6 +388,37 @@ def invalidate_cache_on_directive_write(messages: list, config: dict) -> None:
|
|
|
387
388
|
debug_log(config, f"Directives cache invalidation skipped (error): {e}")
|
|
388
389
|
|
|
389
390
|
|
|
391
|
+
def invalidate_prefetch_buffer_on_directive_write(messages: list, config: dict, session_id: str) -> None:
|
|
392
|
+
"""Drop this session's pending M4 prefetch buffer if this turn wrote a
|
|
393
|
+
directive (create/update/delete/retire).
|
|
394
|
+
|
|
395
|
+
red-team-M3 R2 (BLOCKER): the A4 cache invalidation above keeps the fresh
|
|
396
|
+
<active_directives> block current, but the M4 prefetch buffer carries a
|
|
397
|
+
RECALLED memories block captured at a prior turn's Stop — and a rule/
|
|
398
|
+
directive that was just retired can survive in that snapshot as recalled
|
|
399
|
+
text. Without this, a buffer prefetched while the rule was active would
|
|
400
|
+
re-inject the retired rule on the next turn (the exact §4.4 resurrection
|
|
401
|
+
failure), and `run_prefetch` bailing on an empty recall would leave that
|
|
402
|
+
stale buffer to be served on later turns too. Deleting the buffer + sentinel
|
|
403
|
+
here forces the consumer to fall to the synchronous, always-current path
|
|
404
|
+
(or the explicitly stale-marked fallback) instead of resurrecting it.
|
|
405
|
+
|
|
406
|
+
Only fires when `memoryPrefetchEnabled` is on (the buffer only exists then)
|
|
407
|
+
— a cheap no-op statting two absent files otherwise, so gated to avoid it.
|
|
408
|
+
Self-guarded; never raises (a Stop hook must not wedge a turn).
|
|
409
|
+
"""
|
|
410
|
+
try:
|
|
411
|
+
if not messages or not config.get("memoryPrefetchEnabled", False):
|
|
412
|
+
return
|
|
413
|
+
idx, _text = find_last_human_turn(messages)
|
|
414
|
+
start = idx if idx is not None else -1
|
|
415
|
+
if turn_contains_directive_write(messages, start):
|
|
416
|
+
recall_buffer.invalidate(session_id or "unknown")
|
|
417
|
+
debug_log(config, "Prefetch buffer invalidated — turn wrote a directive (R2 resurrection guard)")
|
|
418
|
+
except Exception as e: # pragma: no cover - defensive; Stop must not wedge
|
|
419
|
+
debug_log(config, f"Prefetch-buffer invalidation skipped (error): {e}")
|
|
420
|
+
|
|
421
|
+
|
|
390
422
|
def read_transcript(transcript_path: str) -> list:
|
|
391
423
|
"""Read a JSONL transcript into a list of message dicts (role/content).
|
|
392
424
|
|
|
@@ -508,6 +540,7 @@ def main():
|
|
|
508
540
|
# and this is not an already-blocked re-fire.
|
|
509
541
|
ttl = config.get("directivesCacheTtlSeconds", DIRECTIVES_CACHE_TTL_SECONDS)
|
|
510
542
|
cache_on = isinstance(ttl, (int, float)) and ttl > 0
|
|
543
|
+
prefetch_on = bool(config.get("memoryPrefetchEnabled", False))
|
|
511
544
|
verify_maybe = (
|
|
512
545
|
config.get("directiveCaptureNudge", True)
|
|
513
546
|
and config.get("directiveCaptureVerify", True)
|
|
@@ -515,7 +548,7 @@ def main():
|
|
|
515
548
|
)
|
|
516
549
|
|
|
517
550
|
messages: list = []
|
|
518
|
-
if cache_on or verify_maybe:
|
|
551
|
+
if cache_on or verify_maybe or prefetch_on:
|
|
519
552
|
messages = read_transcript(hook_input.get("transcript_path", ""))
|
|
520
553
|
|
|
521
554
|
# A4: invalidate the directives cache when this turn wrote a directive, so
|
|
@@ -524,6 +557,15 @@ def main():
|
|
|
524
557
|
if cache_on:
|
|
525
558
|
invalidate_cache_on_directive_write(messages, config)
|
|
526
559
|
|
|
560
|
+
# R2 (BLOCKER): invalidate this session's M4 prefetch buffer when this turn
|
|
561
|
+
# wrote/retired a directive, so a stale pre-retire snapshot can never
|
|
562
|
+
# resurrect the retired rule on a later turn. Self-guarded no-op when
|
|
563
|
+
# prefetch is off.
|
|
564
|
+
if prefetch_on:
|
|
565
|
+
invalidate_prefetch_buffer_on_directive_write(
|
|
566
|
+
messages, config, hook_input.get("session_id") or "unknown"
|
|
567
|
+
)
|
|
568
|
+
|
|
527
569
|
try:
|
|
528
570
|
reason = evaluate(hook_input, config, messages=messages)
|
|
529
571
|
except Exception as e: # never wedge a turn on a verify bug
|