switchroom 0.21.15 → 0.21.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/rules-sentinel-hook.sh +101 -0
- package/dist/agent-scheduler/index.js +1 -0
- package/dist/auth-broker/index.js +1 -0
- package/dist/cli/notion-write-pretool.mjs +1 -0
- package/dist/cli/switchroom.js +1924 -841
- package/dist/host-control/main.js +2 -1
- package/dist/vault/approvals/kernel-server.js +1 -0
- package/dist/vault/broker/server.js +1 -0
- package/package.json +1 -1
- 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 +5 -4
- package/vendor/hindsight-memory/hooks/hooks.json +10 -0
- package/vendor/hindsight-memory/scripts/lib/recall_buffer.py +236 -0
- package/vendor/hindsight-memory/scripts/prefetch.py +156 -0
- package/vendor/hindsight-memory/scripts/recall.py +185 -0
- package/vendor/hindsight-memory/scripts/retain.py +132 -49
- 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_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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# SessionStart hook — Memory v2 M1 rules-block tamper sentinel
|
|
3
|
+
# (carve-M1.md §3, red-team-M1.md §B/§C, MINOR fixes #6/#7).
|
|
4
|
+
#
|
|
5
|
+
# Recomputes the rules block's sha256 sentinel + the mutation-log hash
|
|
6
|
+
# chain and compares against what's embedded in the agent's own
|
|
7
|
+
# CLAUDE.md. Purely advisory in M1 (dark build — no `memory.rules_block`
|
|
8
|
+
# agent exists live yet): this hook exists so the wiring, the read-only
|
|
9
|
+
# discipline, and the fast no-op path are all proven out and tested
|
|
10
|
+
# BEFORE M3 flips the flag on any real agent.
|
|
11
|
+
#
|
|
12
|
+
# STRICT DISCIPLINE (red-team §C, root-owned-overlay EACCES hazard):
|
|
13
|
+
# - READ-ONLY. This script must NEVER write to the agent's tree. A
|
|
14
|
+
# root-invoked write here would leave a root-owned file behind and
|
|
15
|
+
# EACCES the agent's own subsequent writes to it (the exact failure
|
|
16
|
+
# class that dropped clerk's crons for weeks — see this repo's
|
|
17
|
+
# CLAUDE.md "root-tier host access" section). `switchroom memory
|
|
18
|
+
# rule verify` (the CLI path) is the only writer-adjacent tool, and
|
|
19
|
+
# even IT never writes — verify is read-only too.
|
|
20
|
+
# - Prints to STDOUT, not stderr. SessionStart hook stdout is added to
|
|
21
|
+
# the model's context (unlike PreCompact); a stderr message would be
|
|
22
|
+
# silently dropped by Claude Code's hook contract, defeating the
|
|
23
|
+
# entire point of a tamper *sentinel* — see this file's own header
|
|
24
|
+
# precedent in bin/timezone-hook.sh for the additionalContext
|
|
25
|
+
# mechanism this hook reuses (NOT because "no additionalContext
|
|
26
|
+
# emitter exists" — one already does, in timezone-hook.sh and
|
|
27
|
+
# scaffold.ts; this hook simply needs the same JSON shape).
|
|
28
|
+
# - No-ops FAST and SILENTLY when the rules-block markers are absent
|
|
29
|
+
# (the M1/M3-flag-off dark case, and the pre-first-rule case even
|
|
30
|
+
# with the flag on) — never print a divergence notice about a block
|
|
31
|
+
# that was never created.
|
|
32
|
+
#
|
|
33
|
+
# Wiring note (MINOR fix #7): unlike working-state-reload-hook.sh
|
|
34
|
+
# (matcher: "compact" — fires ONLY on compaction), this hook is wired
|
|
35
|
+
# with NO matcher, so it fires on every SessionStart source (startup,
|
|
36
|
+
# resume, clear, fork, compact). That is a deliberate divergence: a
|
|
37
|
+
# tamper sentinel that only checked itself after compaction would miss
|
|
38
|
+
# tampering that happened between a fresh boot and the first compaction
|
|
39
|
+
# (the common case for a short-lived session). The check itself is a
|
|
40
|
+
# single local file read + a hash compare — cheap enough to run on every
|
|
41
|
+
# boot without a latency concern (contrast working-state-reload's
|
|
42
|
+
# network-hop briefing, which specifically justified restricting itself
|
|
43
|
+
# to the compact matcher).
|
|
44
|
+
#
|
|
45
|
+
# Exit code: 0 always. A hook that exits non-zero BLOCKS the turn in
|
|
46
|
+
# Claude Code; a tamper signal must degrade to a visible notice, never
|
|
47
|
+
# a hard stop (the same "failure modes are silent" doctrine as
|
|
48
|
+
# timezone-hook.sh).
|
|
49
|
+
|
|
50
|
+
set -u
|
|
51
|
+
|
|
52
|
+
CLAUDE_MD="${CLAUDE_PROJECT_DIR:-}/CLAUDE.md"
|
|
53
|
+
|
|
54
|
+
if [ -z "${CLAUDE_PROJECT_DIR:-}" ] || [ ! -f "$CLAUDE_MD" ]; then
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Fast no-op: markers absent (dark build / no rules yet). grep -q is a
|
|
59
|
+
# single pass over the file, no subshell fork per line.
|
|
60
|
+
if ! grep -q '<!-- switchroom:rules:begin -->' "$CLAUDE_MD" 2>/dev/null; then
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Delegate the actual sentinel recompute to the CLI verb this module
|
|
65
|
+
# already ships (`switchroom memory rule verify <agent>`), which reuses
|
|
66
|
+
# rules-store.ts's verifyIntegrity — one recompute implementation, not
|
|
67
|
+
# two (a bash reimplementation of the sha256-over-canonical-JSON
|
|
68
|
+
# encoding would be a second place for the two encodings to drift).
|
|
69
|
+
# `switchroom` is expected on PATH inside the agent container image;
|
|
70
|
+
# if it's missing, degrade silently rather than error the turn.
|
|
71
|
+
if ! command -v switchroom >/dev/null 2>&1; then
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
AGENT_NAME="$(basename "${CLAUDE_PROJECT_DIR:-}")"
|
|
76
|
+
VERIFY_OUT="$(switchroom memory rule verify "$AGENT_NAME" 2>&1)"
|
|
77
|
+
VERIFY_EXIT=$?
|
|
78
|
+
|
|
79
|
+
# Exit-code contract (memory-rules.ts `verify`):
|
|
80
|
+
# 0 = clean; 2 = GENUINE TAMPER; any other non-zero = an ENVIRONMENT
|
|
81
|
+
# failure (config unreadable, agent-dir mismatch, withConfigError exit 1,
|
|
82
|
+
# `switchroom` bug). Only code 2 warrants injecting a tamper notice into
|
|
83
|
+
# the model's context — an environment failure that hard-injected "tamper
|
|
84
|
+
# FAILED" would cry wolf on every broken boot and erode the signal. Degrade
|
|
85
|
+
# silently on everything that is not an unambiguous tamper.
|
|
86
|
+
if [ "$VERIFY_EXIT" -ne 2 ]; then
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
MSG="Rules-block tamper sentinel FAILED for \"$AGENT_NAME\": ${VERIFY_OUT}"
|
|
91
|
+
|
|
92
|
+
if command -v jq >/dev/null 2>&1; then
|
|
93
|
+
jq -cn --arg msg "$MSG" \
|
|
94
|
+
'{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:$msg}}'
|
|
95
|
+
else
|
|
96
|
+
ESCAPED=${MSG//\\/\\\\}
|
|
97
|
+
ESCAPED=${ESCAPED//\"/\\\"}
|
|
98
|
+
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$ESCAPED"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
exit 0
|
|
@@ -11118,6 +11118,7 @@ var AgentMemorySchema = exports_external.object({
|
|
|
11118
11118
|
}
|
|
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
|
+
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)."),
|
|
11121
11122
|
disposition: exports_external.object({
|
|
11122
11123
|
skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
|
|
11123
11124
|
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)."),
|
|
@@ -11143,6 +11143,7 @@ var init_schema = __esm(() => {
|
|
|
11143
11143
|
}
|
|
11144
11144
|
}).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry — e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced — 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded — each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
|
|
11145
11145
|
observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
|
|
11146
|
+
rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded — the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false ⇒ byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded — each agent's flip is a deliberate M3 rollout step)."),
|
|
11146
11147
|
disposition: exports_external.object({
|
|
11147
11148
|
skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
|
|
11148
11149
|
literalism: exports_external.number().int().min(1).max(5).optional().describe("How literally the bank reads statements vs inferring intent (1-5; engine default 3)."),
|
|
@@ -11879,6 +11879,7 @@ var AgentMemorySchema = exports_external.object({
|
|
|
11879
11879
|
}
|
|
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
|
+
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)."),
|
|
11882
11883
|
disposition: exports_external.object({
|
|
11883
11884
|
skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
|
|
11884
11885
|
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)."),
|