switchroom 0.20.20 → 0.20.22
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/dist/agent-scheduler/index.js +4 -4
- package/dist/auth-broker/index.js +5 -5
- package/dist/cli/notion-write-pretool.mjs +4 -4
- package/dist/cli/switchroom.js +25 -13
- package/dist/host-control/main.js +6 -6
- package/dist/vault/approvals/kernel-server.js +5 -5
- package/dist/vault/broker/server.js +5 -5
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +9 -9
- package/vendor/hindsight-memory/scripts/drain_pending.py +88 -4
- package/vendor/hindsight-memory/scripts/lib/config.py +112 -11
- package/vendor/hindsight-memory/scripts/recall.py +11 -2
- package/vendor/hindsight-memory/scripts/reconcile_tail.py +36 -0
- package/vendor/hindsight-memory/scripts/retain.py +6 -1
- package/vendor/hindsight-memory/scripts/tests/test_config_retain_env.py +99 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_types_filter.py +81 -0
- package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +113 -0
- package/vendor/hindsight-memory/settings.json +2 -2
- package/vendor/hindsight-memory/tests/test_config.py +8 -3
- package/vendor/hindsight-memory/tests/test_hooks.py +10 -1
- package/vendor/hindsight-memory/tests/test_retain_context.py +69 -0
|
@@ -11128,22 +11128,22 @@ var AgentMemorySchema = exports_external.object({
|
|
|
11128
11128
|
observation_scopes: ObservationScopesSchema,
|
|
11129
11129
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
11130
11130
|
recall: exports_external.object({
|
|
11131
|
-
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget.
|
|
11131
|
+
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. The vendor plugin ships " + "12; switchroom's resolver default is 8 when this is unset, and the " + "live fleet raises it via `defaults.memory.recall.max_memories`. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
11132
11132
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
11133
11133
|
hook_timeout_seconds: exports_external.number().int().min(1).optional().describe("Ceiling (seconds) Claude Code gives the UserPromptSubmit recall " + "hook before killing it. Stamped into the installed plugin's " + "hooks/hooks.json, so it survives `switchroom apply` reinstalling " + "the plugin. Default 12. Raising it lets slow banks finish at the " + "cost of pre-turn dead air; `parallel_deadline_seconds` and " + "`request_timeout_seconds` are both kept under it. A value below " + "3s is raised to 3s and reported: the fan-out deadline must be at " + "least 1s AND still leave 2s of post-deadline headroom, so a " + "lower ceiling admits no usable envelope at all."),
|
|
11134
11134
|
parallel_deadline_seconds: exports_external.number().int().min(1).optional().describe("Shared deadline (seconds) for the whole parallel multi-bank " + "recall fan-out. Slots unfinished when it elapses are abandoned " + "and reported as timed out. Defaults to `hook_timeout_seconds` " + "minus 2s of headroom for block formatting, cache write and " + "stdout flush, so a straggler bank can never push the hook past " + "its ceiling. Set explicitly to override that derivation; a value " + "that would leave less than 2s under the hook ceiling — including " + "one set EQUAL to it — is clamped back to `hook_timeout_seconds` " + "minus 2, and the clamp is reported. Equality is not allowed: at " + "zero headroom the hook is killed mid-write and the turn loses " + "both the memories and the recall_log row explaining why."),
|
|
11135
11135
|
query_max_tokens: exports_external.number().int().min(0).optional().describe("Cap on the number of DISTINCT BM25 terms the recall hook may " + "put on the wire. `recallMaxQueryChars` bounds characters, which " + "is not the cost driver: Hindsight OR-joins every query token " + "into one tsquery and Postgres native FTS ranks the entire " + "matched set before the top-60 heapsort, so cost tracks TERMS. " + "An 800-char composed query is ~96 distinct terms and matched " + "119,510 of 135,565 rows on the `overlord` bank (14.0s for the " + "3-arm " + "BM25 UNION), past the per-bank timeout — 96.8% of that agent's " + "own-bank recalls returned nothing. Plugin default is 24 " + "(measured 48,433 rows / 2.7s on the same bank). Terms are " + "chosen recency-first (the latest turn beats prior context), " + "then by selectivity. 0 disables shaping (rollback lever)."),
|
|
11136
11136
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional().describe("Extra terms dropped from the BM25 recall query, on top of the " + "built-in English stopword list. For BANK-SPECIFIC " + "high-document-frequency words a generic stoplist cannot know " + "about: on `overlord`, `switchroom` matches 20% of the bank and " + "`agent` another 20%, purely because that is what the corpus is " + "about, and each such term drags tens of thousands of rows into " + "the ranking. Defaults to []."),
|
|
11137
11137
|
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request. Even parallelised, each bank carries its own deadline " + "so ONE hung bank returns empty instead of consuming the shared " + "deadline and starving its siblings. The plugin default is 12 " + "(raised from a hardcoded 8 in #3757, which fired on 96.8% of " + "one agent's own-bank recalls). Switchroom defaults it to the " + "effective `parallel_deadline_seconds` instead — 10 at the " + "shipped ceiling — because the shared fan-out deadline is " + "already the tighter outer guard, so a per-bank value above it " + "can never bind. An explicitly configured value above the " + "effective deadline is clamped down to it, and the clamp is " + "reported."),
|
|
11138
|
-
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents
|
|
11139
|
-
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents
|
|
11138
|
+
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents DERIVE this " + "default from the count cap (≈ ⅓ of the effective `max_memories`, " + "floored at 1) so it tracks the cap instead of going stale when the " + "fleet raises it; an explicit value set here still wins over that " + "derived default."),
|
|
11139
|
+
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents DERIVE this default " + "from the count cap (≈ ⅙ of the effective `max_memories`, floored " + "at 1) so it tracks the cap instead of going stale; an explicit " + "value set here still wins. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
|
|
11140
11140
|
min_score: exports_external.number().min(0).optional().describe("Absolute floor on a memory's engine relevance score " + "(`scores.final`) for it to be injected. 0 disables (default, " + "and the shipped fleet behaviour). Exists for one measured " + "failure: when the agent's own bank times out, recall still " + "injects side-bank residue under the banner 'Relevant memories " + "from past conversations' — 98.4% of degraded turns have a best " + "injected score below 0.01, against 28.4% of healthy ones. Six " + "noise memories are worse than none, because the agent cannot " + "tell them apart. Below-floor results are dropped BEFORE " + "rendering, and when the floor empties the set the turn says so " + "rather than going silent. Do NOT read this as a general " + "precision control: `scores.final` is not calibrated across " + "queries, and #3761 measured that an unconditional 0.01 floor " + "empties ~28% of HEALTHY recalls — which is why " + "`min_score_scope` defaults to degraded turns only. Observe " + "`dropped_below_min_score` via `switchroom memory recall-log`."),
|
|
11141
11141
|
min_score_scope: exports_external.enum(["degraded", "all"]).optional().describe('Which turns `min_score` binds on. "degraded" (default) — only ' + "turns where the agent's OWN bank timed out or was unreachable, " + "the population where a below-floor score actually predicts " + "noise and where the agent already receives the degraded-recall " + 'disclosure. "all" — every turn; only for an operator who has ' + "measured their own bank's score distribution, since it " + "re-creates the empty-recall failure of #3541 at any floor " + "calibrated on degraded data. No effect while `min_score` is 0."),
|
|
11142
11142
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
11143
11143
|
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results — e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with the `request_timeout_seconds` per-bank timeout " + "(defaults to the effective `parallel_deadline_seconds`, 10s at " + "the shipped ceiling) and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
11144
11144
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender → extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username — a leading @ is " + "optional — or numeric user_id), merged " + "into its own results — so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
11145
11145
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
11146
|
-
budget: exports_external.enum(["low", "mid", "high"]).optional().describe(
|
|
11146
|
+
budget: exports_external.enum(["low", "mid", "high"]).optional().describe("How deep Hindsight's recall search goes. It tunes the CANDIDATE-" + "POOL depth retrieved before reranking — low (switchroom default) " + "= 100, mid = 300, high = 1000 candidates. The local cross-encoder " + "reranker (ms-marco-MiniLM) runs at EVERY tier; budget does not " + "gate it on or off, and there is no LLM rerank pass in the recall " + "path (the LLM serves reflect/retain/consolidation, not recall " + "ranking). Higher tiers cost more latency purely from the deeper " + "pool: mid measured ~5s of hook latency on real fleet turns (3× " + "the retrieval+rerank pool, not an added model call), the second-" + "largest contributor to perceived dead air after model TTFT. Raise " + "it for an agent whose recall quality matters more than its reply " + "latency (a research or audit role); leave it at low for chat."),
|
|
11147
11147
|
max_tokens: exports_external.number().int().min(1).optional().describe("Token budget for the injected memory block. Default 1024. This " + "is the TOKEN bound; `max_memories` is the separate COUNT bound " + "and the tighter of the two wins. Raise it only alongside " + "`max_memories` — on its own it buys nothing once the count cap " + "binds."),
|
|
11148
11148
|
prefer_observations: exports_external.boolean().optional().describe("Bias recall toward the synthesized `observation` tier, " + "backfilling the slots freed by superseded raw facts for denser " + "coverage inside the same budget. Default true. Set false to " + "rank raw `world`/`experience` facts on equal footing — useful " + "when auditing what the consolidation engine actually stored, or " + "if a bank's observations are stale."),
|
|
11149
11149
|
context_turns: exports_external.number().int().min(1).optional().describe("How many recent human turns are composed into the recall query. " + 'Default 2, so a bare follow-up ("and the port?") embeds with ' + "its antecedent instead of recalling on the pronoun alone. 1 = " + "the latest turn only. Raising it costs BM25 terms, which is the " + "real recall cost driver — `query_max_tokens` still bounds the " + "result, so a large value mostly shifts which terms survive."),
|
|
@@ -11153,22 +11153,22 @@ var init_schema = __esm(() => {
|
|
|
11153
11153
|
observation_scopes: ObservationScopesSchema,
|
|
11154
11154
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
11155
11155
|
recall: exports_external.object({
|
|
11156
|
-
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget.
|
|
11156
|
+
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. The vendor plugin ships " + "12; switchroom's resolver default is 8 when this is unset, and the " + "live fleet raises it via `defaults.memory.recall.max_memories`. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
11157
11157
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
11158
11158
|
hook_timeout_seconds: exports_external.number().int().min(1).optional().describe("Ceiling (seconds) Claude Code gives the UserPromptSubmit recall " + "hook before killing it. Stamped into the installed plugin's " + "hooks/hooks.json, so it survives `switchroom apply` reinstalling " + "the plugin. Default 12. Raising it lets slow banks finish at the " + "cost of pre-turn dead air; `parallel_deadline_seconds` and " + "`request_timeout_seconds` are both kept under it. A value below " + "3s is raised to 3s and reported: the fan-out deadline must be at " + "least 1s AND still leave 2s of post-deadline headroom, so a " + "lower ceiling admits no usable envelope at all."),
|
|
11159
11159
|
parallel_deadline_seconds: exports_external.number().int().min(1).optional().describe("Shared deadline (seconds) for the whole parallel multi-bank " + "recall fan-out. Slots unfinished when it elapses are abandoned " + "and reported as timed out. Defaults to `hook_timeout_seconds` " + "minus 2s of headroom for block formatting, cache write and " + "stdout flush, so a straggler bank can never push the hook past " + "its ceiling. Set explicitly to override that derivation; a value " + "that would leave less than 2s under the hook ceiling — including " + "one set EQUAL to it — is clamped back to `hook_timeout_seconds` " + "minus 2, and the clamp is reported. Equality is not allowed: at " + "zero headroom the hook is killed mid-write and the turn loses " + "both the memories and the recall_log row explaining why."),
|
|
11160
11160
|
query_max_tokens: exports_external.number().int().min(0).optional().describe("Cap on the number of DISTINCT BM25 terms the recall hook may " + "put on the wire. `recallMaxQueryChars` bounds characters, which " + "is not the cost driver: Hindsight OR-joins every query token " + "into one tsquery and Postgres native FTS ranks the entire " + "matched set before the top-60 heapsort, so cost tracks TERMS. " + "An 800-char composed query is ~96 distinct terms and matched " + "119,510 of 135,565 rows on the `overlord` bank (14.0s for the " + "3-arm " + "BM25 UNION), past the per-bank timeout — 96.8% of that agent's " + "own-bank recalls returned nothing. Plugin default is 24 " + "(measured 48,433 rows / 2.7s on the same bank). Terms are " + "chosen recency-first (the latest turn beats prior context), " + "then by selectivity. 0 disables shaping (rollback lever)."),
|
|
11161
11161
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional().describe("Extra terms dropped from the BM25 recall query, on top of the " + "built-in English stopword list. For BANK-SPECIFIC " + "high-document-frequency words a generic stoplist cannot know " + "about: on `overlord`, `switchroom` matches 20% of the bank and " + "`agent` another 20%, purely because that is what the corpus is " + "about, and each such term drags tens of thousands of rows into " + "the ranking. Defaults to []."),
|
|
11162
11162
|
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request. Even parallelised, each bank carries its own deadline " + "so ONE hung bank returns empty instead of consuming the shared " + "deadline and starving its siblings. The plugin default is 12 " + "(raised from a hardcoded 8 in #3757, which fired on 96.8% of " + "one agent's own-bank recalls). Switchroom defaults it to the " + "effective `parallel_deadline_seconds` instead — 10 at the " + "shipped ceiling — because the shared fan-out deadline is " + "already the tighter outer guard, so a per-bank value above it " + "can never bind. An explicitly configured value above the " + "effective deadline is clamped down to it, and the clamp is " + "reported."),
|
|
11163
|
-
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents
|
|
11164
|
-
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents
|
|
11163
|
+
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents DERIVE this " + "default from the count cap (≈ ⅓ of the effective `max_memories`, " + "floored at 1) so it tracks the cap instead of going stale when the " + "fleet raises it; an explicit value set here still wins over that " + "derived default."),
|
|
11164
|
+
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents DERIVE this default " + "from the count cap (≈ ⅙ of the effective `max_memories`, floored " + "at 1) so it tracks the cap instead of going stale; an explicit " + "value set here still wins. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
|
|
11165
11165
|
min_score: exports_external.number().min(0).optional().describe("Absolute floor on a memory's engine relevance score " + "(`scores.final`) for it to be injected. 0 disables (default, " + "and the shipped fleet behaviour). Exists for one measured " + "failure: when the agent's own bank times out, recall still " + "injects side-bank residue under the banner 'Relevant memories " + "from past conversations' — 98.4% of degraded turns have a best " + "injected score below 0.01, against 28.4% of healthy ones. Six " + "noise memories are worse than none, because the agent cannot " + "tell them apart. Below-floor results are dropped BEFORE " + "rendering, and when the floor empties the set the turn says so " + "rather than going silent. Do NOT read this as a general " + "precision control: `scores.final` is not calibrated across " + "queries, and #3761 measured that an unconditional 0.01 floor " + "empties ~28% of HEALTHY recalls — which is why " + "`min_score_scope` defaults to degraded turns only. Observe " + "`dropped_below_min_score` via `switchroom memory recall-log`."),
|
|
11166
11166
|
min_score_scope: exports_external.enum(["degraded", "all"]).optional().describe('Which turns `min_score` binds on. "degraded" (default) — only ' + "turns where the agent's OWN bank timed out or was unreachable, " + "the population where a below-floor score actually predicts " + "noise and where the agent already receives the degraded-recall " + 'disclosure. "all" — every turn; only for an operator who has ' + "measured their own bank's score distribution, since it " + "re-creates the empty-recall failure of #3541 at any floor " + "calibrated on degraded data. No effect while `min_score` is 0."),
|
|
11167
11167
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
11168
11168
|
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results — e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with the `request_timeout_seconds` per-bank timeout " + "(defaults to the effective `parallel_deadline_seconds`, 10s at " + "the shipped ceiling) and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
11169
11169
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender → extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username — a leading @ is " + "optional — or numeric user_id), merged " + "into its own results — so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
11170
11170
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
11171
|
-
budget: exports_external.enum(["low", "mid", "high"]).optional().describe(
|
|
11171
|
+
budget: exports_external.enum(["low", "mid", "high"]).optional().describe("How deep Hindsight's recall search goes. It tunes the CANDIDATE-" + "POOL depth retrieved before reranking — low (switchroom default) " + "= 100, mid = 300, high = 1000 candidates. The local cross-encoder " + "reranker (ms-marco-MiniLM) runs at EVERY tier; budget does not " + "gate it on or off, and there is no LLM rerank pass in the recall " + "path (the LLM serves reflect/retain/consolidation, not recall " + "ranking). Higher tiers cost more latency purely from the deeper " + "pool: mid measured ~5s of hook latency on real fleet turns (3× " + "the retrieval+rerank pool, not an added model call), the second-" + "largest contributor to perceived dead air after model TTFT. Raise " + "it for an agent whose recall quality matters more than its reply " + "latency (a research or audit role); leave it at low for chat."),
|
|
11172
11172
|
max_tokens: exports_external.number().int().min(1).optional().describe("Token budget for the injected memory block. Default 1024. This " + "is the TOKEN bound; `max_memories` is the separate COUNT bound " + "and the tighter of the two wins. Raise it only alongside " + "`max_memories` — on its own it buys nothing once the count cap " + "binds."),
|
|
11173
11173
|
prefer_observations: exports_external.boolean().optional().describe("Bias recall toward the synthesized `observation` tier, " + "backfilling the slots freed by superseded raw facts for denser " + "coverage inside the same budget. Default true. Set false to " + "rank raw `world`/`experience` facts on equal footing — useful " + "when auditing what the consolidation engine actually stored, or " + "if a bank's observations are stale."),
|
|
11174
11174
|
context_turns: exports_external.number().int().min(1).optional().describe("How many recent human turns are composed into the recall query. " + 'Default 2, so a bare follow-up ("and the port?") embeds with ' + "its antecedent instead of recalling on the pronoun alone. 1 = " + "the latest turn only. Raising it costs BM25 terms, which is the " + "real recall cost driver — `query_max_tokens` still bounds the " + "result, so a large value mostly shifts which terms survive."),
|
|
@@ -19033,7 +19033,7 @@ function allocateAgentUid(name) {
|
|
|
19033
19033
|
var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
|
|
19034
19034
|
var HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT = "Relevant memories from past conversations (prioritize recent when " + "conflicting). Only use memories that are directly useful to continue " + "this conversation; ignore the rest:";
|
|
19035
19035
|
var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
19036
|
-
budget: "
|
|
19036
|
+
budget: "mid",
|
|
19037
19037
|
maxTokens: 1024,
|
|
19038
19038
|
preferObservations: true,
|
|
19039
19039
|
contextTurns: 2,
|
|
@@ -11889,22 +11889,22 @@ var AgentMemorySchema = exports_external.object({
|
|
|
11889
11889
|
observation_scopes: ObservationScopesSchema,
|
|
11890
11890
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
11891
11891
|
recall: exports_external.object({
|
|
11892
|
-
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget.
|
|
11892
|
+
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. The vendor plugin ships " + "12; switchroom's resolver default is 8 when this is unset, and the " + "live fleet raises it via `defaults.memory.recall.max_memories`. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
11893
11893
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
11894
11894
|
hook_timeout_seconds: exports_external.number().int().min(1).optional().describe("Ceiling (seconds) Claude Code gives the UserPromptSubmit recall " + "hook before killing it. Stamped into the installed plugin's " + "hooks/hooks.json, so it survives `switchroom apply` reinstalling " + "the plugin. Default 12. Raising it lets slow banks finish at the " + "cost of pre-turn dead air; `parallel_deadline_seconds` and " + "`request_timeout_seconds` are both kept under it. A value below " + "3s is raised to 3s and reported: the fan-out deadline must be at " + "least 1s AND still leave 2s of post-deadline headroom, so a " + "lower ceiling admits no usable envelope at all."),
|
|
11895
11895
|
parallel_deadline_seconds: exports_external.number().int().min(1).optional().describe("Shared deadline (seconds) for the whole parallel multi-bank " + "recall fan-out. Slots unfinished when it elapses are abandoned " + "and reported as timed out. Defaults to `hook_timeout_seconds` " + "minus 2s of headroom for block formatting, cache write and " + "stdout flush, so a straggler bank can never push the hook past " + "its ceiling. Set explicitly to override that derivation; a value " + "that would leave less than 2s under the hook ceiling \u2014 including " + "one set EQUAL to it \u2014 is clamped back to `hook_timeout_seconds` " + "minus 2, and the clamp is reported. Equality is not allowed: at " + "zero headroom the hook is killed mid-write and the turn loses " + "both the memories and the recall_log row explaining why."),
|
|
11896
11896
|
query_max_tokens: exports_external.number().int().min(0).optional().describe("Cap on the number of DISTINCT BM25 terms the recall hook may " + "put on the wire. `recallMaxQueryChars` bounds characters, which " + "is not the cost driver: Hindsight OR-joins every query token " + "into one tsquery and Postgres native FTS ranks the entire " + "matched set before the top-60 heapsort, so cost tracks TERMS. " + "An 800-char composed query is ~96 distinct terms and matched " + "119,510 of 135,565 rows on the `overlord` bank (14.0s for the " + "3-arm " + "BM25 UNION), past the per-bank timeout \u2014 96.8% of that agent's " + "own-bank recalls returned nothing. Plugin default is 24 " + "(measured 48,433 rows / 2.7s on the same bank). Terms are " + "chosen recency-first (the latest turn beats prior context), " + "then by selectivity. 0 disables shaping (rollback lever)."),
|
|
11897
11897
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional().describe("Extra terms dropped from the BM25 recall query, on top of the " + "built-in English stopword list. For BANK-SPECIFIC " + "high-document-frequency words a generic stoplist cannot know " + "about: on `overlord`, `switchroom` matches 20% of the bank and " + "`agent` another 20%, purely because that is what the corpus is " + "about, and each such term drags tens of thousands of rows into " + "the ranking. Defaults to []."),
|
|
11898
11898
|
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request. Even parallelised, each bank carries its own deadline " + "so ONE hung bank returns empty instead of consuming the shared " + "deadline and starving its siblings. The plugin default is 12 " + "(raised from a hardcoded 8 in #3757, which fired on 96.8% of " + "one agent's own-bank recalls). Switchroom defaults it to the " + "effective `parallel_deadline_seconds` instead \u2014 10 at the " + "shipped ceiling \u2014 because the shared fan-out deadline is " + "already the tighter outer guard, so a per-bank value above it " + "can never bind. An explicitly configured value above the " + "effective deadline is clamped down to it, and the clamp is " + "reported."),
|
|
11899
|
-
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "\u2014 the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents
|
|
11900
|
-
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` \u2014 same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents
|
|
11899
|
+
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "\u2014 the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents DERIVE this " + "default from the count cap (\u2248 \u2153 of the effective `max_memories`, " + "floored at 1) so it tracks the cap instead of going stale when the " + "fleet raises it; an explicit value set here still wins over that " + "derived default."),
|
|
11900
|
+
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` \u2014 same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents DERIVE this default " + "from the count cap (\u2248 \u2159 of the effective `max_memories`, floored " + "at 1) so it tracks the cap instead of going stale; an explicit " + "value set here still wins. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
|
|
11901
11901
|
min_score: exports_external.number().min(0).optional().describe("Absolute floor on a memory's engine relevance score " + "(`scores.final`) for it to be injected. 0 disables (default, " + "and the shipped fleet behaviour). Exists for one measured " + "failure: when the agent's own bank times out, recall still " + "injects side-bank residue under the banner 'Relevant memories " + "from past conversations' \u2014 98.4% of degraded turns have a best " + "injected score below 0.01, against 28.4% of healthy ones. Six " + "noise memories are worse than none, because the agent cannot " + "tell them apart. Below-floor results are dropped BEFORE " + "rendering, and when the floor empties the set the turn says so " + "rather than going silent. Do NOT read this as a general " + "precision control: `scores.final` is not calibrated across " + "queries, and #3761 measured that an unconditional 0.01 floor " + "empties ~28% of HEALTHY recalls \u2014 which is why " + "`min_score_scope` defaults to degraded turns only. Observe " + "`dropped_below_min_score` via `switchroom memory recall-log`."),
|
|
11902
11902
|
min_score_scope: exports_external.enum(["degraded", "all"]).optional().describe('Which turns `min_score` binds on. "degraded" (default) \u2014 only ' + "turns where the agent's OWN bank timed out or was unreachable, " + "the population where a below-floor score actually predicts " + "noise and where the agent already receives the degraded-recall " + 'disclosure. "all" \u2014 every turn; only for an operator who has ' + "measured their own bank's score distribution, since it " + "re-creates the empty-recall failure of #3541 at any floor " + "calibrated on degraded data. No effect while `min_score` is 0."),
|
|
11903
11903
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] \u2014 the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
11904
11904
|
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results \u2014 e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with the `request_timeout_seconds` per-bank timeout " + "(defaults to the effective `parallel_deadline_seconds`, 10s at " + "the shipped ceiling) and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
11905
11905
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender \u2192 extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username \u2014 a leading @ is " + "optional \u2014 or numeric user_id), merged " + "into its own results \u2014 so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
11906
11906
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true \u2014 saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
11907
|
-
budget: exports_external.enum(["low", "mid", "high"]).optional().describe(
|
|
11907
|
+
budget: exports_external.enum(["low", "mid", "high"]).optional().describe("How deep Hindsight's recall search goes. It tunes the CANDIDATE-" + "POOL depth retrieved before reranking \u2014 low (switchroom default) " + "= 100, mid = 300, high = 1000 candidates. The local cross-encoder " + "reranker (ms-marco-MiniLM) runs at EVERY tier; budget does not " + "gate it on or off, and there is no LLM rerank pass in the recall " + "path (the LLM serves reflect/retain/consolidation, not recall " + "ranking). Higher tiers cost more latency purely from the deeper " + "pool: mid measured ~5s of hook latency on real fleet turns (3\u00d7 " + "the retrieval+rerank pool, not an added model call), the second-" + "largest contributor to perceived dead air after model TTFT. Raise " + "it for an agent whose recall quality matters more than its reply " + "latency (a research or audit role); leave it at low for chat."),
|
|
11908
11908
|
max_tokens: exports_external.number().int().min(1).optional().describe("Token budget for the injected memory block. Default 1024. This " + "is the TOKEN bound; `max_memories` is the separate COUNT bound " + "and the tighter of the two wins. Raise it only alongside " + "`max_memories` \u2014 on its own it buys nothing once the count cap " + "binds."),
|
|
11909
11909
|
prefer_observations: exports_external.boolean().optional().describe("Bias recall toward the synthesized `observation` tier, " + "backfilling the slots freed by superseded raw facts for denser " + "coverage inside the same budget. Default true. Set false to " + "rank raw `world`/`experience` facts on equal footing \u2014 useful " + "when auditing what the consolidation engine actually stored, or " + "if a bank's observations are stale."),
|
|
11910
11910
|
context_turns: exports_external.number().int().min(1).optional().describe("How many recent human turns are composed into the recall query. " + 'Default 2, so a bare follow-up ("and the port?") embeds with ' + "its antecedent instead of recalling on the pronoun alone. 1 = " + "the latest turn only. Raising it costs BM25 terms, which is the " + "real recall cost driver \u2014 `query_max_tokens` still bounds the " + "result, so a large value mostly shifts which terms survive."),
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.20.
|
|
2123
|
+
var VERSION = "0.20.22", COMMIT_SHA = "ad3f50bd";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -13754,22 +13754,22 @@ var init_schema = __esm(() => {
|
|
|
13754
13754
|
observation_scopes: ObservationScopesSchema,
|
|
13755
13755
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
13756
13756
|
recall: exports_external.object({
|
|
13757
|
-
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget.
|
|
13757
|
+
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. The vendor plugin ships " + "12; switchroom's resolver default is 8 when this is unset, and the " + "live fleet raises it via `defaults.memory.recall.max_memories`. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
13758
13758
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
13759
13759
|
hook_timeout_seconds: exports_external.number().int().min(1).optional().describe("Ceiling (seconds) Claude Code gives the UserPromptSubmit recall " + "hook before killing it. Stamped into the installed plugin's " + "hooks/hooks.json, so it survives `switchroom apply` reinstalling " + "the plugin. Default 12. Raising it lets slow banks finish at the " + "cost of pre-turn dead air; `parallel_deadline_seconds` and " + "`request_timeout_seconds` are both kept under it. A value below " + "3s is raised to 3s and reported: the fan-out deadline must be at " + "least 1s AND still leave 2s of post-deadline headroom, so a " + "lower ceiling admits no usable envelope at all."),
|
|
13760
13760
|
parallel_deadline_seconds: exports_external.number().int().min(1).optional().describe("Shared deadline (seconds) for the whole parallel multi-bank " + "recall fan-out. Slots unfinished when it elapses are abandoned " + "and reported as timed out. Defaults to `hook_timeout_seconds` " + "minus 2s of headroom for block formatting, cache write and " + "stdout flush, so a straggler bank can never push the hook past " + "its ceiling. Set explicitly to override that derivation; a value " + "that would leave less than 2s under the hook ceiling \u2014 including " + "one set EQUAL to it \u2014 is clamped back to `hook_timeout_seconds` " + "minus 2, and the clamp is reported. Equality is not allowed: at " + "zero headroom the hook is killed mid-write and the turn loses " + "both the memories and the recall_log row explaining why."),
|
|
13761
13761
|
query_max_tokens: exports_external.number().int().min(0).optional().describe("Cap on the number of DISTINCT BM25 terms the recall hook may " + "put on the wire. `recallMaxQueryChars` bounds characters, which " + "is not the cost driver: Hindsight OR-joins every query token " + "into one tsquery and Postgres native FTS ranks the entire " + "matched set before the top-60 heapsort, so cost tracks TERMS. " + "An 800-char composed query is ~96 distinct terms and matched " + "119,510 of 135,565 rows on the `overlord` bank (14.0s for the " + "3-arm " + "BM25 UNION), past the per-bank timeout \u2014 96.8% of that agent's " + "own-bank recalls returned nothing. Plugin default is 24 " + "(measured 48,433 rows / 2.7s on the same bank). Terms are " + "chosen recency-first (the latest turn beats prior context), " + "then by selectivity. 0 disables shaping (rollback lever)."),
|
|
13762
13762
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional().describe("Extra terms dropped from the BM25 recall query, on top of the " + "built-in English stopword list. For BANK-SPECIFIC " + "high-document-frequency words a generic stoplist cannot know " + "about: on `overlord`, `switchroom` matches 20% of the bank and " + "`agent` another 20%, purely because that is what the corpus is " + "about, and each such term drags tens of thousands of rows into " + "the ranking. Defaults to []."),
|
|
13763
13763
|
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request. Even parallelised, each bank carries its own deadline " + "so ONE hung bank returns empty instead of consuming the shared " + "deadline and starving its siblings. The plugin default is 12 " + "(raised from a hardcoded 8 in #3757, which fired on 96.8% of " + "one agent's own-bank recalls). Switchroom defaults it to the " + "effective `parallel_deadline_seconds` instead \u2014 10 at the " + "shipped ceiling \u2014 because the shared fan-out deadline is " + "already the tighter outer guard, so a per-bank value above it " + "can never bind. An explicitly configured value above the " + "effective deadline is clamped down to it, and the clamp is " + "reported."),
|
|
13764
|
-
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "\u2014 the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents
|
|
13765
|
-
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` \u2014 same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents
|
|
13764
|
+
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "\u2014 the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents DERIVE this " + "default from the count cap (\u2248 \u2153 of the effective `max_memories`, " + "floored at 1) so it tracks the cap instead of going stale when the " + "fleet raises it; an explicit value set here still wins over that " + "derived default."),
|
|
13765
|
+
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` \u2014 same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents DERIVE this default " + "from the count cap (\u2248 \u2159 of the effective `max_memories`, floored " + "at 1) so it tracks the cap instead of going stale; an explicit " + "value set here still wins. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
|
|
13766
13766
|
min_score: exports_external.number().min(0).optional().describe("Absolute floor on a memory's engine relevance score " + "(`scores.final`) for it to be injected. 0 disables (default, " + "and the shipped fleet behaviour). Exists for one measured " + "failure: when the agent's own bank times out, recall still " + "injects side-bank residue under the banner 'Relevant memories " + "from past conversations' \u2014 98.4% of degraded turns have a best " + "injected score below 0.01, against 28.4% of healthy ones. Six " + "noise memories are worse than none, because the agent cannot " + "tell them apart. Below-floor results are dropped BEFORE " + "rendering, and when the floor empties the set the turn says so " + "rather than going silent. Do NOT read this as a general " + "precision control: `scores.final` is not calibrated across " + "queries, and #3761 measured that an unconditional 0.01 floor " + "empties ~28% of HEALTHY recalls \u2014 which is why " + "`min_score_scope` defaults to degraded turns only. Observe " + "`dropped_below_min_score` via `switchroom memory recall-log`."),
|
|
13767
13767
|
min_score_scope: exports_external.enum(["degraded", "all"]).optional().describe('Which turns `min_score` binds on. "degraded" (default) \u2014 only ' + "turns where the agent's OWN bank timed out or was unreachable, " + "the population where a below-floor score actually predicts " + "noise and where the agent already receives the degraded-recall " + 'disclosure. "all" \u2014 every turn; only for an operator who has ' + "measured their own bank's score distribution, since it " + "re-creates the empty-recall failure of #3541 at any floor " + "calibrated on degraded data. No effect while `min_score` is 0."),
|
|
13768
13768
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] \u2014 the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
13769
13769
|
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results \u2014 e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with the `request_timeout_seconds` per-bank timeout " + "(defaults to the effective `parallel_deadline_seconds`, 10s at " + "the shipped ceiling) and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
13770
13770
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender \u2192 extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username \u2014 a leading @ is " + "optional \u2014 or numeric user_id), merged " + "into its own results \u2014 so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
13771
13771
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true \u2014 saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
13772
|
-
budget: exports_external.enum(["low", "mid", "high"]).optional().describe(
|
|
13772
|
+
budget: exports_external.enum(["low", "mid", "high"]).optional().describe("How deep Hindsight's recall search goes. It tunes the CANDIDATE-" + "POOL depth retrieved before reranking \u2014 low (switchroom default) " + "= 100, mid = 300, high = 1000 candidates. The local cross-encoder " + "reranker (ms-marco-MiniLM) runs at EVERY tier; budget does not " + "gate it on or off, and there is no LLM rerank pass in the recall " + "path (the LLM serves reflect/retain/consolidation, not recall " + "ranking). Higher tiers cost more latency purely from the deeper " + "pool: mid measured ~5s of hook latency on real fleet turns (3\u00d7 " + "the retrieval+rerank pool, not an added model call), the second-" + "largest contributor to perceived dead air after model TTFT. Raise " + "it for an agent whose recall quality matters more than its reply " + "latency (a research or audit role); leave it at low for chat."),
|
|
13773
13773
|
max_tokens: exports_external.number().int().min(1).optional().describe("Token budget for the injected memory block. Default 1024. This " + "is the TOKEN bound; `max_memories` is the separate COUNT bound " + "and the tighter of the two wins. Raise it only alongside " + "`max_memories` \u2014 on its own it buys nothing once the count cap " + "binds."),
|
|
13774
13774
|
prefer_observations: exports_external.boolean().optional().describe("Bias recall toward the synthesized `observation` tier, " + "backfilling the slots freed by superseded raw facts for denser " + "coverage inside the same budget. Default true. Set false to " + "rank raw `world`/`experience` facts on equal footing \u2014 useful " + "when auditing what the consolidation engine actually stored, or " + "if a bank's observations are stale."),
|
|
13775
13775
|
context_turns: exports_external.number().int().min(1).optional().describe("How many recent human turns are composed into the recall query. " + 'Default 2, so a bare follow-up ("and the port?") embeds with ' + "its antecedent instead of recalling on the pronoun alone. 1 = " + "the latest turn only. Raising it costs BM25 terms, which is the " + "real recall cost driver \u2014 `query_max_tokens` still bounds the " + "result, so a large value mostly shifts which terms survive."),
|
|
@@ -21194,7 +21194,7 @@ var init_hindsight_recall_passthrough = __esm(() => {
|
|
|
21194
21194
|
RECALL_BUDGETS = ["low", "mid", "high"];
|
|
21195
21195
|
RECALL_TAGS_MATCH_MODES = ["any", "all", "any_strict", "all_strict"];
|
|
21196
21196
|
RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
21197
|
-
budget: "
|
|
21197
|
+
budget: "mid",
|
|
21198
21198
|
maxTokens: 1024,
|
|
21199
21199
|
preferObservations: true,
|
|
21200
21200
|
contextTurns: 2,
|
|
@@ -30161,6 +30161,16 @@ function applyHindsightHooksOverrides(pluginDestPath, tunables) {
|
|
|
30161
30161
|
return;
|
|
30162
30162
|
writeFileSyncIfChanged(hooksPath, next);
|
|
30163
30163
|
}
|
|
30164
|
+
function recallSlotFloorFromCap(maxMemories, divisor) {
|
|
30165
|
+
const cap = Number.isFinite(maxMemories) && maxMemories > 0 ? maxMemories : DEFAULT_RECALL_MAX_MEMORIES;
|
|
30166
|
+
return Math.max(1, Math.round(cap / divisor));
|
|
30167
|
+
}
|
|
30168
|
+
function defaultRecallOwnBankMinSlots(maxMemories) {
|
|
30169
|
+
return recallSlotFloorFromCap(maxMemories, RECALL_OWN_BANK_SLOT_DIVISOR);
|
|
30170
|
+
}
|
|
30171
|
+
function defaultRecallAdditionalBankMinSlots(maxMemories) {
|
|
30172
|
+
return recallSlotFloorFromCap(maxMemories, RECALL_ADDITIONAL_BANK_SLOT_DIVISOR);
|
|
30173
|
+
}
|
|
30164
30174
|
function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings) {
|
|
30165
30175
|
const settingsPath = join12(pluginDestPath, "settings.json");
|
|
30166
30176
|
if (!existsSync18(settingsPath))
|
|
@@ -30192,8 +30202,8 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, re
|
|
|
30192
30202
|
settings.recallParallelDeadlineSeconds = recallTunables.parallelDeadlineSeconds;
|
|
30193
30203
|
settings.recallRequestTimeoutSeconds = recallTunables.requestTimeoutSeconds;
|
|
30194
30204
|
settings.recallTypes = ["world", "experience", "observation"];
|
|
30195
|
-
settings.recallOwnBankMinSlots =
|
|
30196
|
-
settings.recallAdditionalBankMinSlots =
|
|
30205
|
+
settings.recallOwnBankMinSlots = defaultRecallOwnBankMinSlots(recallCaps.maxMemories);
|
|
30206
|
+
settings.recallAdditionalBankMinSlots = defaultRecallAdditionalBankMinSlots(recallCaps.maxMemories);
|
|
30197
30207
|
settings.recallSkipTrivial = true;
|
|
30198
30208
|
settings.directiveCaptureNudge = true;
|
|
30199
30209
|
settings.recallTagWeights = { ...HINDSIGHT_RECALL_TAG_WEIGHT_SEED };
|
|
@@ -30569,8 +30579,9 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
30569
30579
|
const hindsightRecallQueryMaxTokens = agentConfig.memory?.recall?.query_max_tokens ?? HINDSIGHT_RECALL_QUERY_MAX_TOKENS_DEFAULT;
|
|
30570
30580
|
const rawRecallQueryStopTerms = agentConfig.memory?.recall?.query_stop_terms;
|
|
30571
30581
|
const hindsightRecallQueryStopTermsJson = JSON.stringify(rawRecallQueryStopTerms ?? []);
|
|
30572
|
-
const
|
|
30573
|
-
const
|
|
30582
|
+
const hindsightRecallEffectiveMaxMemories = resolveHindsightRecallCaps(agentConfig.memory?.recall).maxMemories;
|
|
30583
|
+
const hindsightRecallOwnBankMinSlots = agentConfig.memory?.recall?.own_bank_min_slots ?? defaultRecallOwnBankMinSlots(hindsightRecallEffectiveMaxMemories);
|
|
30584
|
+
const hindsightRecallAdditionalBankMinSlots = agentConfig.memory?.recall?.additional_bank_min_slots ?? defaultRecallAdditionalBankMinSlots(hindsightRecallEffectiveMaxMemories);
|
|
30574
30585
|
const hindsightRecallMinScore = agentConfig.memory?.recall?.min_score ?? HINDSIGHT_RECALL_MIN_SCORE_DEFAULT;
|
|
30575
30586
|
const hindsightRecallMinScoreScope = agentConfig.memory?.recall?.min_score_scope ?? HINDSIGHT_RECALL_MIN_SCORE_SCOPE_DEFAULT;
|
|
30576
30587
|
const hindsightRecallPassthrough = resolveHindsightRecallPassthrough(agentConfig.memory?.recall);
|
|
@@ -31574,8 +31585,9 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
|
|
|
31574
31585
|
const hindsightRecallQueryMaxTokens = agentConfig.memory?.recall?.query_max_tokens ?? HINDSIGHT_RECALL_QUERY_MAX_TOKENS_DEFAULT;
|
|
31575
31586
|
const rawRecallQueryStopTerms = agentConfig.memory?.recall?.query_stop_terms;
|
|
31576
31587
|
const hindsightRecallQueryStopTermsJson = JSON.stringify(rawRecallQueryStopTerms ?? []);
|
|
31577
|
-
const
|
|
31578
|
-
const
|
|
31588
|
+
const hindsightRecallEffectiveMaxMemories = resolveHindsightRecallCaps(agentConfig.memory?.recall).maxMemories;
|
|
31589
|
+
const hindsightRecallOwnBankMinSlots = agentConfig.memory?.recall?.own_bank_min_slots ?? defaultRecallOwnBankMinSlots(hindsightRecallEffectiveMaxMemories);
|
|
31590
|
+
const hindsightRecallAdditionalBankMinSlots = agentConfig.memory?.recall?.additional_bank_min_slots ?? defaultRecallAdditionalBankMinSlots(hindsightRecallEffectiveMaxMemories);
|
|
31579
31591
|
const hindsightRecallMinScore = agentConfig.memory?.recall?.min_score ?? HINDSIGHT_RECALL_MIN_SCORE_DEFAULT;
|
|
31580
31592
|
const hindsightRecallMinScoreScope = agentConfig.memory?.recall?.min_score_scope ?? HINDSIGHT_RECALL_MIN_SCORE_SCOPE_DEFAULT;
|
|
31581
31593
|
const hindsightRecallPassthrough = resolveHindsightRecallPassthrough(agentConfig.memory?.recall);
|
|
@@ -32762,7 +32774,7 @@ plainer wins.
|
|
|
32762
32774
|
Every turn that answers a user message ends with a user-visible \`reply\`
|
|
32763
32775
|
\u2014 Telegram is all the user sees; your terminal output
|
|
32764
32776
|
never reaches them.`, TELEGRAM_ENV_PLACEHOLDER = `# Set your bot token: TELEGRAM_BOT_TOKEN=your-token-here
|
|
32765
|
-
`, SWITCHROOM_TELEGRAM_MCP_TOOLS, HINDSIGHT_MCP_TOOLS, AGENT_CONFIG_MCP_TOOLS, HOSTD_MCP_TOOLS, WEBKITE_MCP_TOOLS, CONTEXT7_MCP_TOOLS, CONTEXT7_BLANKET_TOKENS, LEGACY_SWITCHROOM_MCP_TOKENS, LEGACY_HOSTD_BLANKET_TOKENS, DEFAULT_READ_ONLY_PREAPPROVED_TOOLS, NATIVE_WEBSEARCH_PREAPPROVED_TOOLS, WEBKITE_FLEET_DENY_TOOLS, INTERACTIVE_TUI_FLEET_DENY_TOOLS, WEBKITE_BINARY_CONTAINER_PATH = "/usr/local/bin/webkite", SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-5", SWITCHROOM_DEFAULT_THINKING_EFFORT = "low", CLAUDE_MD_YOURS_PLACEHOLDER, pendingBankOps, SWITCHROOM_OWNED_SETTINGS_KEYS, ALL_BUILTIN_TOOLS, HINDSIGHT_DEFAULT_RETAIN_EVERY_N_TURNS = 3, HINDSIGHT_DEFAULT_RETAIN_OVERLAP_TURNS = 1,
|
|
32777
|
+
`, SWITCHROOM_TELEGRAM_MCP_TOOLS, HINDSIGHT_MCP_TOOLS, AGENT_CONFIG_MCP_TOOLS, HOSTD_MCP_TOOLS, WEBKITE_MCP_TOOLS, CONTEXT7_MCP_TOOLS, CONTEXT7_BLANKET_TOKENS, LEGACY_SWITCHROOM_MCP_TOKENS, LEGACY_HOSTD_BLANKET_TOKENS, DEFAULT_READ_ONLY_PREAPPROVED_TOOLS, NATIVE_WEBSEARCH_PREAPPROVED_TOOLS, WEBKITE_FLEET_DENY_TOOLS, INTERACTIVE_TUI_FLEET_DENY_TOOLS, WEBKITE_BINARY_CONTAINER_PATH = "/usr/local/bin/webkite", SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-5", SWITCHROOM_DEFAULT_THINKING_EFFORT = "low", CLAUDE_MD_YOURS_PLACEHOLDER, pendingBankOps, SWITCHROOM_OWNED_SETTINGS_KEYS, ALL_BUILTIN_TOOLS, HINDSIGHT_DEFAULT_RETAIN_EVERY_N_TURNS = 3, HINDSIGHT_DEFAULT_RETAIN_OVERLAP_TURNS = 1, RECALL_OWN_BANK_SLOT_DIVISOR = 3, RECALL_ADDITIONAL_BANK_SLOT_DIVISOR = 6, HINDSIGHT_RECALL_MIN_SCORE_DEFAULT = 0, HINDSIGHT_RECALL_MIN_SCORE_SCOPE_DEFAULT = "degraded", VALID_SETTINGS_DEFAULT_MODES, SWITCHROOM_DEFAULT_PERMISSION_MODE = "acceptEdits", DOCKER_TELEGRAM_PLUGIN_PATH = "/opt/switchroom/telegram-plugin", DOCKER_HOOKS_PATH, DOCKER_BUNDLED_HOOKS_PATH = "/opt/switchroom/hooks", DOCKER_BIN_PATH = "/opt/switchroom/bin", DOCKER_SECURITY_PLUGIN_PATH = "/opt/switchroom/security-plugin", DOCKER_CONFIG_PATH = "/state/config/switchroom.yaml", DOCKER_SWITCHROOM_CLI_PATH = "/usr/local/bin/switchroom", DOCKER_AUTH_BROKER_SOCKET = "/run/switchroom/auth-broker/sock", DOCKER_VAULT_BROKER_SOCKET = "/run/switchroom/broker/sock", DOCKER_AGENT_HOME = "/state/agent/home", CONTEXT7_MCP_URL = "https://mcp.context7.com/mcp", INTEGRATION_MCP_RESOLVERS;
|
|
32766
32778
|
var init_scaffold = __esm(() => {
|
|
32767
32779
|
init_source();
|
|
32768
32780
|
init_cron_unit_name();
|
|
@@ -11153,22 +11153,22 @@ var init_schema = __esm(() => {
|
|
|
11153
11153
|
observation_scopes: ObservationScopesSchema,
|
|
11154
11154
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
11155
11155
|
recall: exports_external.object({
|
|
11156
|
-
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget.
|
|
11156
|
+
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. The vendor plugin ships " + "12; switchroom's resolver default is 8 when this is unset, and the " + "live fleet raises it via `defaults.memory.recall.max_memories`. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
11157
11157
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
11158
11158
|
hook_timeout_seconds: exports_external.number().int().min(1).optional().describe("Ceiling (seconds) Claude Code gives the UserPromptSubmit recall " + "hook before killing it. Stamped into the installed plugin's " + "hooks/hooks.json, so it survives `switchroom apply` reinstalling " + "the plugin. Default 12. Raising it lets slow banks finish at the " + "cost of pre-turn dead air; `parallel_deadline_seconds` and " + "`request_timeout_seconds` are both kept under it. A value below " + "3s is raised to 3s and reported: the fan-out deadline must be at " + "least 1s AND still leave 2s of post-deadline headroom, so a " + "lower ceiling admits no usable envelope at all."),
|
|
11159
11159
|
parallel_deadline_seconds: exports_external.number().int().min(1).optional().describe("Shared deadline (seconds) for the whole parallel multi-bank " + "recall fan-out. Slots unfinished when it elapses are abandoned " + "and reported as timed out. Defaults to `hook_timeout_seconds` " + "minus 2s of headroom for block formatting, cache write and " + "stdout flush, so a straggler bank can never push the hook past " + "its ceiling. Set explicitly to override that derivation; a value " + "that would leave less than 2s under the hook ceiling — including " + "one set EQUAL to it — is clamped back to `hook_timeout_seconds` " + "minus 2, and the clamp is reported. Equality is not allowed: at " + "zero headroom the hook is killed mid-write and the turn loses " + "both the memories and the recall_log row explaining why."),
|
|
11160
11160
|
query_max_tokens: exports_external.number().int().min(0).optional().describe("Cap on the number of DISTINCT BM25 terms the recall hook may " + "put on the wire. `recallMaxQueryChars` bounds characters, which " + "is not the cost driver: Hindsight OR-joins every query token " + "into one tsquery and Postgres native FTS ranks the entire " + "matched set before the top-60 heapsort, so cost tracks TERMS. " + "An 800-char composed query is ~96 distinct terms and matched " + "119,510 of 135,565 rows on the `overlord` bank (14.0s for the " + "3-arm " + "BM25 UNION), past the per-bank timeout — 96.8% of that agent's " + "own-bank recalls returned nothing. Plugin default is 24 " + "(measured 48,433 rows / 2.7s on the same bank). Terms are " + "chosen recency-first (the latest turn beats prior context), " + "then by selectivity. 0 disables shaping (rollback lever)."),
|
|
11161
11161
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional().describe("Extra terms dropped from the BM25 recall query, on top of the " + "built-in English stopword list. For BANK-SPECIFIC " + "high-document-frequency words a generic stoplist cannot know " + "about: on `overlord`, `switchroom` matches 20% of the bank and " + "`agent` another 20%, purely because that is what the corpus is " + "about, and each such term drags tens of thousands of rows into " + "the ranking. Defaults to []."),
|
|
11162
11162
|
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request. Even parallelised, each bank carries its own deadline " + "so ONE hung bank returns empty instead of consuming the shared " + "deadline and starving its siblings. The plugin default is 12 " + "(raised from a hardcoded 8 in #3757, which fired on 96.8% of " + "one agent's own-bank recalls). Switchroom defaults it to the " + "effective `parallel_deadline_seconds` instead — 10 at the " + "shipped ceiling — because the shared fan-out deadline is " + "already the tighter outer guard, so a per-bank value above it " + "can never bind. An explicitly configured value above the " + "effective deadline is clamped down to it, and the clamp is " + "reported."),
|
|
11163
|
-
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents
|
|
11164
|
-
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents
|
|
11163
|
+
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents DERIVE this " + "default from the count cap (≈ ⅓ of the effective `max_memories`, " + "floored at 1) so it tracks the cap instead of going stale when the " + "fleet raises it; an explicit value set here still wins over that " + "derived default."),
|
|
11164
|
+
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents DERIVE this default " + "from the count cap (≈ ⅙ of the effective `max_memories`, floored " + "at 1) so it tracks the cap instead of going stale; an explicit " + "value set here still wins. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
|
|
11165
11165
|
min_score: exports_external.number().min(0).optional().describe("Absolute floor on a memory's engine relevance score " + "(`scores.final`) for it to be injected. 0 disables (default, " + "and the shipped fleet behaviour). Exists for one measured " + "failure: when the agent's own bank times out, recall still " + "injects side-bank residue under the banner 'Relevant memories " + "from past conversations' — 98.4% of degraded turns have a best " + "injected score below 0.01, against 28.4% of healthy ones. Six " + "noise memories are worse than none, because the agent cannot " + "tell them apart. Below-floor results are dropped BEFORE " + "rendering, and when the floor empties the set the turn says so " + "rather than going silent. Do NOT read this as a general " + "precision control: `scores.final` is not calibrated across " + "queries, and #3761 measured that an unconditional 0.01 floor " + "empties ~28% of HEALTHY recalls — which is why " + "`min_score_scope` defaults to degraded turns only. Observe " + "`dropped_below_min_score` via `switchroom memory recall-log`."),
|
|
11166
11166
|
min_score_scope: exports_external.enum(["degraded", "all"]).optional().describe('Which turns `min_score` binds on. "degraded" (default) — only ' + "turns where the agent's OWN bank timed out or was unreachable, " + "the population where a below-floor score actually predicts " + "noise and where the agent already receives the degraded-recall " + 'disclosure. "all" — every turn; only for an operator who has ' + "measured their own bank's score distribution, since it " + "re-creates the empty-recall failure of #3541 at any floor " + "calibrated on degraded data. No effect while `min_score` is 0."),
|
|
11167
11167
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
11168
11168
|
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results — e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with the `request_timeout_seconds` per-bank timeout " + "(defaults to the effective `parallel_deadline_seconds`, 10s at " + "the shipped ceiling) and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
11169
11169
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender → extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username — a leading @ is " + "optional — or numeric user_id), merged " + "into its own results — so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
11170
11170
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
11171
|
-
budget: exports_external.enum(["low", "mid", "high"]).optional().describe(
|
|
11171
|
+
budget: exports_external.enum(["low", "mid", "high"]).optional().describe("How deep Hindsight's recall search goes. It tunes the CANDIDATE-" + "POOL depth retrieved before reranking — low (switchroom default) " + "= 100, mid = 300, high = 1000 candidates. The local cross-encoder " + "reranker (ms-marco-MiniLM) runs at EVERY tier; budget does not " + "gate it on or off, and there is no LLM rerank pass in the recall " + "path (the LLM serves reflect/retain/consolidation, not recall " + "ranking). Higher tiers cost more latency purely from the deeper " + "pool: mid measured ~5s of hook latency on real fleet turns (3× " + "the retrieval+rerank pool, not an added model call), the second-" + "largest contributor to perceived dead air after model TTFT. Raise " + "it for an agent whose recall quality matters more than its reply " + "latency (a research or audit role); leave it at low for chat."),
|
|
11172
11172
|
max_tokens: exports_external.number().int().min(1).optional().describe("Token budget for the injected memory block. Default 1024. This " + "is the TOKEN bound; `max_memories` is the separate COUNT bound " + "and the tighter of the two wins. Raise it only alongside " + "`max_memories` — on its own it buys nothing once the count cap " + "binds."),
|
|
11173
11173
|
prefer_observations: exports_external.boolean().optional().describe("Bias recall toward the synthesized `observation` tier, " + "backfilling the slots freed by superseded raw facts for denser " + "coverage inside the same budget. Default true. Set false to " + "rank raw `world`/`experience` facts on equal footing — useful " + "when auditing what the consolidation engine actually stored, or " + "if a bank's observations are stale."),
|
|
11174
11174
|
context_turns: exports_external.number().int().min(1).optional().describe("How many recent human turns are composed into the recall query. " + 'Default 2, so a bare follow-up ("and the port?") embeds with ' + "its antecedent instead of recalling on the pronoun alone. 1 = " + "the latest turn only. Raising it costs BM25 terms, which is the " + "real recall cost driver — `query_max_tokens` still bounds the " + "result, so a large value mostly shifts which terms survive."),
|
|
@@ -21565,13 +21565,13 @@ function allocateAgentUid(name) {
|
|
|
21565
21565
|
}
|
|
21566
21566
|
|
|
21567
21567
|
// src/build-info.ts
|
|
21568
|
-
var VERSION = "0.20.
|
|
21568
|
+
var VERSION = "0.20.22";
|
|
21569
21569
|
|
|
21570
21570
|
// src/setup/hindsight-recall-passthrough.ts
|
|
21571
21571
|
var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
|
|
21572
21572
|
var HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT = "Relevant memories from past conversations (prioritize recent when " + "conflicting). Only use memories that are directly useful to continue " + "this conversation; ignore the rest:";
|
|
21573
21573
|
var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
21574
|
-
budget: "
|
|
21574
|
+
budget: "mid",
|
|
21575
21575
|
maxTokens: 1024,
|
|
21576
21576
|
preferObservations: true,
|
|
21577
21577
|
contextTurns: 2,
|
|
@@ -4593,22 +4593,22 @@ var init_schema = __esm(() => {
|
|
|
4593
4593
|
observation_scopes: ObservationScopesSchema,
|
|
4594
4594
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
4595
4595
|
recall: exports_external.object({
|
|
4596
|
-
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget.
|
|
4596
|
+
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. The vendor plugin ships " + "12; switchroom's resolver default is 8 when this is unset, and the " + "live fleet raises it via `defaults.memory.recall.max_memories`. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
4597
4597
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
4598
4598
|
hook_timeout_seconds: exports_external.number().int().min(1).optional().describe("Ceiling (seconds) Claude Code gives the UserPromptSubmit recall " + "hook before killing it. Stamped into the installed plugin's " + "hooks/hooks.json, so it survives `switchroom apply` reinstalling " + "the plugin. Default 12. Raising it lets slow banks finish at the " + "cost of pre-turn dead air; `parallel_deadline_seconds` and " + "`request_timeout_seconds` are both kept under it. A value below " + "3s is raised to 3s and reported: the fan-out deadline must be at " + "least 1s AND still leave 2s of post-deadline headroom, so a " + "lower ceiling admits no usable envelope at all."),
|
|
4599
4599
|
parallel_deadline_seconds: exports_external.number().int().min(1).optional().describe("Shared deadline (seconds) for the whole parallel multi-bank " + "recall fan-out. Slots unfinished when it elapses are abandoned " + "and reported as timed out. Defaults to `hook_timeout_seconds` " + "minus 2s of headroom for block formatting, cache write and " + "stdout flush, so a straggler bank can never push the hook past " + "its ceiling. Set explicitly to override that derivation; a value " + "that would leave less than 2s under the hook ceiling — including " + "one set EQUAL to it — is clamped back to `hook_timeout_seconds` " + "minus 2, and the clamp is reported. Equality is not allowed: at " + "zero headroom the hook is killed mid-write and the turn loses " + "both the memories and the recall_log row explaining why."),
|
|
4600
4600
|
query_max_tokens: exports_external.number().int().min(0).optional().describe("Cap on the number of DISTINCT BM25 terms the recall hook may " + "put on the wire. `recallMaxQueryChars` bounds characters, which " + "is not the cost driver: Hindsight OR-joins every query token " + "into one tsquery and Postgres native FTS ranks the entire " + "matched set before the top-60 heapsort, so cost tracks TERMS. " + "An 800-char composed query is ~96 distinct terms and matched " + "119,510 of 135,565 rows on the `overlord` bank (14.0s for the " + "3-arm " + "BM25 UNION), past the per-bank timeout — 96.8% of that agent's " + "own-bank recalls returned nothing. Plugin default is 24 " + "(measured 48,433 rows / 2.7s on the same bank). Terms are " + "chosen recency-first (the latest turn beats prior context), " + "then by selectivity. 0 disables shaping (rollback lever)."),
|
|
4601
4601
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional().describe("Extra terms dropped from the BM25 recall query, on top of the " + "built-in English stopword list. For BANK-SPECIFIC " + "high-document-frequency words a generic stoplist cannot know " + "about: on `overlord`, `switchroom` matches 20% of the bank and " + "`agent` another 20%, purely because that is what the corpus is " + "about, and each such term drags tens of thousands of rows into " + "the ranking. Defaults to []."),
|
|
4602
4602
|
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request. Even parallelised, each bank carries its own deadline " + "so ONE hung bank returns empty instead of consuming the shared " + "deadline and starving its siblings. The plugin default is 12 " + "(raised from a hardcoded 8 in #3757, which fired on 96.8% of " + "one agent's own-bank recalls). Switchroom defaults it to the " + "effective `parallel_deadline_seconds` instead — 10 at the " + "shipped ceiling — because the shared fan-out deadline is " + "already the tighter outer guard, so a per-bank value above it " + "can never bind. An explicitly configured value above the " + "effective deadline is clamped down to it, and the clamp is " + "reported."),
|
|
4603
|
-
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents
|
|
4604
|
-
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents
|
|
4603
|
+
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents DERIVE this " + "default from the count cap (≈ ⅓ of the effective `max_memories`, " + "floored at 1) so it tracks the cap instead of going stale when the " + "fleet raises it; an explicit value set here still wins over that " + "derived default."),
|
|
4604
|
+
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents DERIVE this default " + "from the count cap (≈ ⅙ of the effective `max_memories`, floored " + "at 1) so it tracks the cap instead of going stale; an explicit " + "value set here still wins. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
|
|
4605
4605
|
min_score: exports_external.number().min(0).optional().describe("Absolute floor on a memory's engine relevance score " + "(`scores.final`) for it to be injected. 0 disables (default, " + "and the shipped fleet behaviour). Exists for one measured " + "failure: when the agent's own bank times out, recall still " + "injects side-bank residue under the banner 'Relevant memories " + "from past conversations' — 98.4% of degraded turns have a best " + "injected score below 0.01, against 28.4% of healthy ones. Six " + "noise memories are worse than none, because the agent cannot " + "tell them apart. Below-floor results are dropped BEFORE " + "rendering, and when the floor empties the set the turn says so " + "rather than going silent. Do NOT read this as a general " + "precision control: `scores.final` is not calibrated across " + "queries, and #3761 measured that an unconditional 0.01 floor " + "empties ~28% of HEALTHY recalls — which is why " + "`min_score_scope` defaults to degraded turns only. Observe " + "`dropped_below_min_score` via `switchroom memory recall-log`."),
|
|
4606
4606
|
min_score_scope: exports_external.enum(["degraded", "all"]).optional().describe('Which turns `min_score` binds on. "degraded" (default) — only ' + "turns where the agent's OWN bank timed out or was unreachable, " + "the population where a below-floor score actually predicts " + "noise and where the agent already receives the degraded-recall " + 'disclosure. "all" — every turn; only for an operator who has ' + "measured their own bank's score distribution, since it " + "re-creates the empty-recall failure of #3541 at any floor " + "calibrated on degraded data. No effect while `min_score` is 0."),
|
|
4607
4607
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
4608
4608
|
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results — e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with the `request_timeout_seconds` per-bank timeout " + "(defaults to the effective `parallel_deadline_seconds`, 10s at " + "the shipped ceiling) and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
4609
4609
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender → extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username — a leading @ is " + "optional — or numeric user_id), merged " + "into its own results — so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
4610
4610
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
4611
|
-
budget: exports_external.enum(["low", "mid", "high"]).optional().describe(
|
|
4611
|
+
budget: exports_external.enum(["low", "mid", "high"]).optional().describe("How deep Hindsight's recall search goes. It tunes the CANDIDATE-" + "POOL depth retrieved before reranking — low (switchroom default) " + "= 100, mid = 300, high = 1000 candidates. The local cross-encoder " + "reranker (ms-marco-MiniLM) runs at EVERY tier; budget does not " + "gate it on or off, and there is no LLM rerank pass in the recall " + "path (the LLM serves reflect/retain/consolidation, not recall " + "ranking). Higher tiers cost more latency purely from the deeper " + "pool: mid measured ~5s of hook latency on real fleet turns (3× " + "the retrieval+rerank pool, not an added model call), the second-" + "largest contributor to perceived dead air after model TTFT. Raise " + "it for an agent whose recall quality matters more than its reply " + "latency (a research or audit role); leave it at low for chat."),
|
|
4612
4612
|
max_tokens: exports_external.number().int().min(1).optional().describe("Token budget for the injected memory block. Default 1024. This " + "is the TOKEN bound; `max_memories` is the separate COUNT bound " + "and the tighter of the two wins. Raise it only alongside " + "`max_memories` — on its own it buys nothing once the count cap " + "binds."),
|
|
4613
4613
|
prefer_observations: exports_external.boolean().optional().describe("Bias recall toward the synthesized `observation` tier, " + "backfilling the slots freed by superseded raw facts for denser " + "coverage inside the same budget. Default true. Set false to " + "rank raw `world`/`experience` facts on equal footing — useful " + "when auditing what the consolidation engine actually stored, or " + "if a bank's observations are stale."),
|
|
4614
4614
|
context_turns: exports_external.number().int().min(1).optional().describe("How many recent human turns are composed into the recall query. " + 'Default 2, so a bare follow-up ("and the port?") embeds with ' + "its antecedent instead of recalling on the pronoun alone. 1 = " + "the latest turn only. Raising it costs BM25 terms, which is the " + "real recall cost driver — `query_max_tokens` still bounds the " + "result, so a large value mostly shifts which terms survive."),
|
|
@@ -19466,7 +19466,7 @@ function allocateAgentUid(name) {
|
|
|
19466
19466
|
var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
|
|
19467
19467
|
var HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT = "Relevant memories from past conversations (prioritize recent when " + "conflicting). Only use memories that are directly useful to continue " + "this conversation; ignore the rest:";
|
|
19468
19468
|
var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
19469
|
-
budget: "
|
|
19469
|
+
budget: "mid",
|
|
19470
19470
|
maxTokens: 1024,
|
|
19471
19471
|
preferObservations: true,
|
|
19472
19472
|
contextTurns: 2,
|
|
@@ -4189,22 +4189,22 @@ var init_schema = __esm(() => {
|
|
|
4189
4189
|
observation_scopes: ObservationScopesSchema,
|
|
4190
4190
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
4191
4191
|
recall: exports_external.object({
|
|
4192
|
-
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget.
|
|
4192
|
+
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. The vendor plugin ships " + "12; switchroom's resolver default is 8 when this is unset, and the " + "live fleet raises it via `defaults.memory.recall.max_memories`. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
4193
4193
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
4194
4194
|
hook_timeout_seconds: exports_external.number().int().min(1).optional().describe("Ceiling (seconds) Claude Code gives the UserPromptSubmit recall " + "hook before killing it. Stamped into the installed plugin's " + "hooks/hooks.json, so it survives `switchroom apply` reinstalling " + "the plugin. Default 12. Raising it lets slow banks finish at the " + "cost of pre-turn dead air; `parallel_deadline_seconds` and " + "`request_timeout_seconds` are both kept under it. A value below " + "3s is raised to 3s and reported: the fan-out deadline must be at " + "least 1s AND still leave 2s of post-deadline headroom, so a " + "lower ceiling admits no usable envelope at all."),
|
|
4195
4195
|
parallel_deadline_seconds: exports_external.number().int().min(1).optional().describe("Shared deadline (seconds) for the whole parallel multi-bank " + "recall fan-out. Slots unfinished when it elapses are abandoned " + "and reported as timed out. Defaults to `hook_timeout_seconds` " + "minus 2s of headroom for block formatting, cache write and " + "stdout flush, so a straggler bank can never push the hook past " + "its ceiling. Set explicitly to override that derivation; a value " + "that would leave less than 2s under the hook ceiling — including " + "one set EQUAL to it — is clamped back to `hook_timeout_seconds` " + "minus 2, and the clamp is reported. Equality is not allowed: at " + "zero headroom the hook is killed mid-write and the turn loses " + "both the memories and the recall_log row explaining why."),
|
|
4196
4196
|
query_max_tokens: exports_external.number().int().min(0).optional().describe("Cap on the number of DISTINCT BM25 terms the recall hook may " + "put on the wire. `recallMaxQueryChars` bounds characters, which " + "is not the cost driver: Hindsight OR-joins every query token " + "into one tsquery and Postgres native FTS ranks the entire " + "matched set before the top-60 heapsort, so cost tracks TERMS. " + "An 800-char composed query is ~96 distinct terms and matched " + "119,510 of 135,565 rows on the `overlord` bank (14.0s for the " + "3-arm " + "BM25 UNION), past the per-bank timeout — 96.8% of that agent's " + "own-bank recalls returned nothing. Plugin default is 24 " + "(measured 48,433 rows / 2.7s on the same bank). Terms are " + "chosen recency-first (the latest turn beats prior context), " + "then by selectivity. 0 disables shaping (rollback lever)."),
|
|
4197
4197
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional().describe("Extra terms dropped from the BM25 recall query, on top of the " + "built-in English stopword list. For BANK-SPECIFIC " + "high-document-frequency words a generic stoplist cannot know " + "about: on `overlord`, `switchroom` matches 20% of the bank and " + "`agent` another 20%, purely because that is what the corpus is " + "about, and each such term drags tens of thousands of rows into " + "the ranking. Defaults to []."),
|
|
4198
4198
|
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request. Even parallelised, each bank carries its own deadline " + "so ONE hung bank returns empty instead of consuming the shared " + "deadline and starving its siblings. The plugin default is 12 " + "(raised from a hardcoded 8 in #3757, which fired on 96.8% of " + "one agent's own-bank recalls). Switchroom defaults it to the " + "effective `parallel_deadline_seconds` instead — 10 at the " + "shipped ceiling — because the shared fan-out deadline is " + "already the tighter outer guard, so a per-bank value above it " + "can never bind. An explicitly configured value above the " + "effective deadline is clamped down to it, and the clamp is " + "reported."),
|
|
4199
|
-
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents
|
|
4200
|
-
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents
|
|
4199
|
+
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "— the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents DERIVE this " + "default from the count cap (≈ ⅓ of the effective `max_memories`, " + "floored at 1) so it tracks the cap instead of going stale when the " + "fleet raises it; an explicit value set here still wins over that " + "derived default."),
|
|
4200
|
+
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` — same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents DERIVE this default " + "from the count cap (≈ ⅙ of the effective `max_memories`, floored " + "at 1) so it tracks the cap instead of going stale; an explicit " + "value set here still wins. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
|
|
4201
4201
|
min_score: exports_external.number().min(0).optional().describe("Absolute floor on a memory's engine relevance score " + "(`scores.final`) for it to be injected. 0 disables (default, " + "and the shipped fleet behaviour). Exists for one measured " + "failure: when the agent's own bank times out, recall still " + "injects side-bank residue under the banner 'Relevant memories " + "from past conversations' — 98.4% of degraded turns have a best " + "injected score below 0.01, against 28.4% of healthy ones. Six " + "noise memories are worse than none, because the agent cannot " + "tell them apart. Below-floor results are dropped BEFORE " + "rendering, and when the floor empties the set the turn says so " + "rather than going silent. Do NOT read this as a general " + "precision control: `scores.final` is not calibrated across " + "queries, and #3761 measured that an unconditional 0.01 floor " + "empties ~28% of HEALTHY recalls — which is why " + "`min_score_scope` defaults to degraded turns only. Observe " + "`dropped_below_min_score` via `switchroom memory recall-log`."),
|
|
4202
4202
|
min_score_scope: exports_external.enum(["degraded", "all"]).optional().describe('Which turns `min_score` binds on. "degraded" (default) — only ' + "turns where the agent's OWN bank timed out or was unreachable, " + "the population where a below-floor score actually predicts " + "noise and where the agent already receives the degraded-recall " + 'disclosure. "all" — every turn; only for an operator who has ' + "measured their own bank's score distribution, since it " + "re-creates the empty-recall failure of #3541 at any floor " + "calibrated on degraded data. No effect while `min_score` is 0."),
|
|
4203
4203
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
4204
4204
|
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results — e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with the `request_timeout_seconds` per-bank timeout " + "(defaults to the effective `parallel_deadline_seconds`, 10s at " + "the shipped ceiling) and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
4205
4205
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender → extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username — a leading @ is " + "optional — or numeric user_id), merged " + "into its own results — so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
4206
4206
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
4207
|
-
budget: exports_external.enum(["low", "mid", "high"]).optional().describe(
|
|
4207
|
+
budget: exports_external.enum(["low", "mid", "high"]).optional().describe("How deep Hindsight's recall search goes. It tunes the CANDIDATE-" + "POOL depth retrieved before reranking — low (switchroom default) " + "= 100, mid = 300, high = 1000 candidates. The local cross-encoder " + "reranker (ms-marco-MiniLM) runs at EVERY tier; budget does not " + "gate it on or off, and there is no LLM rerank pass in the recall " + "path (the LLM serves reflect/retain/consolidation, not recall " + "ranking). Higher tiers cost more latency purely from the deeper " + "pool: mid measured ~5s of hook latency on real fleet turns (3× " + "the retrieval+rerank pool, not an added model call), the second-" + "largest contributor to perceived dead air after model TTFT. Raise " + "it for an agent whose recall quality matters more than its reply " + "latency (a research or audit role); leave it at low for chat."),
|
|
4208
4208
|
max_tokens: exports_external.number().int().min(1).optional().describe("Token budget for the injected memory block. Default 1024. This " + "is the TOKEN bound; `max_memories` is the separate COUNT bound " + "and the tighter of the two wins. Raise it only alongside " + "`max_memories` — on its own it buys nothing once the count cap " + "binds."),
|
|
4209
4209
|
prefer_observations: exports_external.boolean().optional().describe("Bias recall toward the synthesized `observation` tier, " + "backfilling the slots freed by superseded raw facts for denser " + "coverage inside the same budget. Default true. Set false to " + "rank raw `world`/`experience` facts on equal footing — useful " + "when auditing what the consolidation engine actually stored, or " + "if a bank's observations are stale."),
|
|
4210
4210
|
context_turns: exports_external.number().int().min(1).optional().describe("How many recent human turns are composed into the recall query. " + 'Default 2, so a bare follow-up ("and the port?") embeds with ' + "its antecedent instead of recalling on the pronoun alone. 1 = " + "the latest turn only. Raising it costs BM25 terms, which is the " + "real recall cost driver — `query_max_tokens` still bounds the " + "result, so a large value mostly shifts which terms survive."),
|
|
@@ -19707,7 +19707,7 @@ function allocateAgentUid(name) {
|
|
|
19707
19707
|
var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
|
|
19708
19708
|
var HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT = "Relevant memories from past conversations (prioritize recent when " + "conflicting). Only use memories that are directly useful to continue " + "this conversation; ignore the rest:";
|
|
19709
19709
|
var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
19710
|
-
budget: "
|
|
19710
|
+
budget: "mid",
|
|
19711
19711
|
maxTokens: 1024,
|
|
19712
19712
|
preferObservations: true,
|
|
19713
19713
|
contextTurns: 2,
|
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.20.
|
|
4
|
+
"version": "0.20.22",
|
|
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": {
|