switchroom 0.19.26 → 0.19.27

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.
Files changed (77) hide show
  1. package/bin/git-agent-attribution-hook.sh +144 -0
  2. package/dist/agent-scheduler/index.js +55 -0
  3. package/dist/auth-broker/index.js +116 -6
  4. package/dist/cli/notion-write-pretool.mjs +55 -0
  5. package/dist/cli/switchroom.js +2055 -1193
  6. package/dist/host-control/main.js +117 -7
  7. package/dist/vault/approvals/kernel-server.js +114 -6
  8. package/dist/vault/broker/server.js +114 -6
  9. package/package.json +6 -2
  10. package/profiles/_base/cron-session.sh.hbs +8 -0
  11. package/profiles/_base/start.sh.hbs +105 -0
  12. package/telegram-plugin/card-layout.ts +328 -0
  13. package/telegram-plugin/dist/bridge/bridge.js +93 -1
  14. package/telegram-plugin/dist/gateway/gateway.js +2009 -1166
  15. package/telegram-plugin/dist/server.js +96 -1
  16. package/telegram-plugin/edit-flood-fuse.ts +637 -56
  17. package/telegram-plugin/flood-429-ledger.ts +526 -0
  18. package/telegram-plugin/flood-circuit-breaker.ts +18 -0
  19. package/telegram-plugin/gateway/flood-reply-queue.ts +168 -0
  20. package/telegram-plugin/gateway/gateway.ts +58 -68
  21. package/telegram-plugin/gateway/narrative-lane.ts +14 -0
  22. package/telegram-plugin/gateway/outbound-send-path.ts +36 -0
  23. package/telegram-plugin/gateway/outbox-sweep.ts +183 -6
  24. package/telegram-plugin/gateway/pinned-message-handler.ts +12 -16
  25. package/telegram-plugin/gateway/status-pin-retarget.ts +72 -36
  26. package/telegram-plugin/gateway/status-pin-store.ts +58 -9
  27. package/telegram-plugin/gateway/worker-pin-reaper.ts +56 -7
  28. package/telegram-plugin/llm-error-present.ts +61 -2
  29. package/telegram-plugin/model-unavailable.ts +8 -0
  30. package/telegram-plugin/operator-events.ts +72 -5
  31. package/telegram-plugin/outbound-class.ts +81 -0
  32. package/telegram-plugin/provider-credit.ts +237 -0
  33. package/telegram-plugin/scripts/bun-test-ci.sh +36 -6
  34. package/telegram-plugin/send-gate.ts +24 -2
  35. package/telegram-plugin/status-no-truncate.ts +10 -48
  36. package/telegram-plugin/status-pin-driver.ts +33 -45
  37. package/telegram-plugin/status-pin.ts +18 -1
  38. package/telegram-plugin/tests/card-golden.test.ts +69 -0
  39. package/telegram-plugin/tests/card-lifecycle-render.test.ts +362 -0
  40. package/telegram-plugin/tests/card-type-distinguishability.test.ts +187 -164
  41. package/telegram-plugin/tests/card-variants.golden.txt +211 -0
  42. package/telegram-plugin/tests/card-variants.ts +366 -0
  43. package/telegram-plugin/tests/edit-flood-fuse-ban-awareness.test.ts +316 -0
  44. package/telegram-plugin/tests/edit-flood-fuse-default-deny.test.ts +319 -0
  45. package/telegram-plugin/tests/edit-flood-fuse.test.ts +11 -2
  46. package/telegram-plugin/tests/feed-edit-rate-ceiling.test.ts +462 -0
  47. package/telegram-plugin/tests/fixtures/real-429-stream.ts +220 -0
  48. package/telegram-plugin/tests/flood-429-ledger.test.ts +278 -0
  49. package/telegram-plugin/tests/flood-429-recorder-wiring.test.ts +128 -0
  50. package/telegram-plugin/tests/flood-reply-queue.test.ts +418 -0
  51. package/telegram-plugin/tests/outbox-sweep-flood-breaker.test.ts +221 -0
  52. package/telegram-plugin/tests/pinned-card-collapse.test.ts +19 -24
  53. package/telegram-plugin/tests/pinned-message-handler.test.ts +15 -15
  54. package/telegram-plugin/tests/provider-credit-402.test.ts +243 -0
  55. package/telegram-plugin/tests/status-pin-api.test.ts +11 -11
  56. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +36 -37
  57. package/telegram-plugin/tests/status-pin-lifecycle.test.ts +602 -0
  58. package/telegram-plugin/tests/status-pin-retarget.test.ts +90 -62
  59. package/telegram-plugin/tests/status-pin-service-message-suppression.test.ts +7 -3
  60. package/telegram-plugin/tests/status-pin-store.test.ts +109 -60
  61. package/telegram-plugin/tests/status-pin.test.ts +56 -5
  62. package/telegram-plugin/tests/test-runner-coverage.test.ts +133 -0
  63. package/telegram-plugin/tests/worker-activity-feed.test.ts +12 -10
  64. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +23 -29
  65. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +56 -59
  66. package/telegram-plugin/tests/worker-feed-terminal-edit-class.test.ts +335 -0
  67. package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +1 -1
  68. package/telegram-plugin/tool-activity-summary.ts +239 -365
  69. package/telegram-plugin/uat/assertions.ts +22 -11
  70. package/telegram-plugin/uat/feed-matcher.test.ts +24 -17
  71. package/telegram-plugin/worker-activity-feed.ts +105 -47
  72. package/vendor/hindsight-memory/CLAUDE.md +45 -0
  73. package/vendor/hindsight-memory/scripts/lib/config.py +33 -0
  74. package/vendor/hindsight-memory/scripts/recall.py +176 -7
  75. package/vendor/hindsight-memory/scripts/tests/test_config_recall_passthrough_env.py +170 -0
  76. package/vendor/hindsight-memory/scripts/tests/test_recall_min_score.py +464 -0
  77. package/vendor/hindsight-memory/settings.json +1 -1
@@ -0,0 +1,144 @@
1
+ #!/bin/sh
2
+ # git-agent-attribution-hook.sh — deterministic `Switchroom-Agent:` /
3
+ # `Switchroom-Model:` commit trailers for work done inside a switchroom
4
+ # agent container.
5
+ #
6
+ # WHY THIS IS A HOOK AND NOT A RULE IN CLAUDE.md
7
+ # ----------------------------------------------
8
+ # Every PR and commit in the fleet's repos is authored by the operator's
9
+ # GitHub account, because agents commit with the operator's git identity.
10
+ # That is deliberate and stays. The cost is that `git log` cannot answer
11
+ # "which agent, on which model, produced this" — the only machine marker
12
+ # is the generic `Co-authored-by: Claude ...` trailer, which names no
13
+ # agent.
14
+ #
15
+ # A prompt rule ("always add these trailers") is model-dependent: it is
16
+ # forgotten under compaction, dropped by a sub-agent that never read the
17
+ # rule, and silently absent forever after. This hook makes the trailers a
18
+ # property of the ENVIRONMENT instead: any `git commit` run inside an
19
+ # agent container gets them, from `$SWITCHROOM_AGENT_NAME` and the live
20
+ # session model, with no model cooperation at all.
21
+ #
22
+ # WHY `prepare-commit-msg` AND NOT `commit-msg`
23
+ # ---------------------------------------------
24
+ # `git commit --no-verify` bypasses `pre-commit` and `commit-msg`. It does
25
+ # NOT bypass `prepare-commit-msg` (verified against git 2.47). Agents reach
26
+ # for `--no-verify` routinely to get past a slow or broken repo hook, so a
27
+ # `commit-msg` implementation would be one habitual flag away from silent
28
+ # non-attribution. `prepare-commit-msg` also runs for `git merge` and
29
+ # `git commit --amend`, and it puts the trailers in the message BEFORE the
30
+ # editor, so they are visible rather than injected behind the author's back.
31
+ #
32
+ # ACTIVATION
33
+ # ----------
34
+ # `docker/Dockerfile.agent` installs this file (read-only, in the image, at
35
+ # a path no agent can rewrite) as
36
+ # `/opt/switchroom/git-hooks/prepare-commit-msg`, and the scaffolded
37
+ # `start.sh` sets `git config --global core.hooksPath
38
+ # /opt/switchroom/git-hooks` on every boot. Global config means it applies
39
+ # to EVERY repo the agent clones, including worktrees, without per-repo
40
+ # setup — which is the point: a new clone must not be able to start out
41
+ # un-attributed.
42
+ #
43
+ # REPO-LOCAL HOOK CHAINING
44
+ # ------------------------
45
+ # `core.hooksPath` REPLACES `.git/hooks` wholesale, so a repo that installs
46
+ # its own `prepare-commit-msg` into `.git/hooks` (the `pre-commit`
47
+ # framework does exactly this) would silently stop running it. We therefore
48
+ # delegate to `$GIT_DIR/hooks/prepare-commit-msg` FIRST, propagate a
49
+ # non-zero exit from it, and only then append our trailers — so our
50
+ # trailers are the last word and cannot be clobbered by the repo hook
51
+ # rewriting the message. (husky and friends set `core.hooksPath` LOCALLY,
52
+ # and local config beats global, so those repos never reach this file.)
53
+ #
54
+ # NOT ADDED HERE: `Co-authored-by:`
55
+ # ---------------------------------
56
+ # Deliberate. `Co-authored-by: Claude ...` is the independent "a model
57
+ # wrote this" marker that `scripts/check-agent-attribution-trailers.mjs`
58
+ # keys off to decide whether the Switchroom-* trailers are REQUIRED. If
59
+ # this hook emitted it too, then disabling the hook would remove the
60
+ # marker and the evidence of its own absence in the same stroke, and CI
61
+ # could never go red. Keeping the two producers separate (model writes
62
+ # Co-authored-by, environment writes Switchroom-*) is what gives the CI
63
+ # check teeth.
64
+ #
65
+ # Exit code: always 0 unless a chained repo hook failed. Attribution must
66
+ # never be the reason a commit cannot be made.
67
+
68
+ set -u
69
+
70
+ MSG_FILE="${1-}"
71
+ COMMIT_SOURCE="${2-}"
72
+
73
+ [ -n "$MSG_FILE" ] || exit 0
74
+ [ -f "$MSG_FILE" ] || exit 0
75
+
76
+ # --- 1. Chain to the repo-local hook this one displaced -------------------
77
+ _git_dir="$(git rev-parse --git-dir 2>/dev/null || true)"
78
+ if [ -n "$_git_dir" ] && [ -x "$_git_dir/hooks/prepare-commit-msg" ]; then
79
+ "$_git_dir/hooks/prepare-commit-msg" "$@" || exit $?
80
+ fi
81
+
82
+ # --- 2. Are we in an agent container? -------------------------------------
83
+ # `$SWITCHROOM_AGENT_NAME` is set by compose for every agent container and
84
+ # by nothing else. Absent ⇒ a human at a laptop, or a stray copy of this
85
+ # script: do nothing at all. A human commit must never be stamped with an
86
+ # agent name it did not come from.
87
+ AGENT="${SWITCHROOM_AGENT_NAME:-}"
88
+ [ -n "$AGENT" ] || exit 0
89
+
90
+ # `SWITCHROOM_ATTRIBUTION_DISABLE=1` is the documented, greppable escape
91
+ # hatch for the rare case where a trailer genuinely must not be written
92
+ # (e.g. replaying an upstream patch verbatim). It is intentionally an env
93
+ # var and not a config knob, so its use is confined to one command.
94
+ [ "${SWITCHROOM_ATTRIBUTION_DISABLE:-0}" = "1" ] && exit 0
95
+
96
+ # --- 3. Resolve the model -------------------------------------------------
97
+ # Chain, most specific first:
98
+ # 1. SWITCHROOM_SESSION_MODEL — exported by start.sh immediately before
99
+ # `exec claude`, from the SAME `$_EFFECTIVE_MODEL` the session is
100
+ # actually launched on (so a `/model` override is reflected).
101
+ # 2. SWITCHROOM_AGENT_MODEL — the configured default, written into the
102
+ # container env by compose. Covers shells that did not inherit
103
+ # start.sh's export (a bare `docker exec`).
104
+ # 3. "unknown" — never empty: a present-but-unknown trailer still proves
105
+ # the hook ran, and an empty trailer value would read as a bug.
106
+ MODEL="${SWITCHROOM_SESSION_MODEL:-}"
107
+ [ -n "$MODEL" ] || MODEL="${SWITCHROOM_AGENT_MODEL:-}"
108
+ [ -n "$MODEL" ] || MODEL="unknown"
109
+
110
+ # --- 4. Sanitize ----------------------------------------------------------
111
+ # A git trailer value is single-line by definition. Both inputs are
112
+ # operator-controlled rather than attacker-controlled, but they land
113
+ # verbatim in an argv passed to `git interpret-trailers`, so clamp them to
114
+ # a conservative charset and length regardless: anything else is replaced,
115
+ # not silently dropped, so a malformed value is visible in the log rather
116
+ # than mistaken for a different agent.
117
+ _sanitize() {
118
+ printf '%s' "$1" \
119
+ | tr -d '\n\r' \
120
+ | sed 's/[^A-Za-z0-9._@:+/-]/_/g' \
121
+ | cut -c1-100
122
+ }
123
+ AGENT="$(_sanitize "$AGENT")"
124
+ MODEL="$(_sanitize "$MODEL")"
125
+ [ -n "$AGENT" ] || exit 0
126
+ [ -n "$MODEL" ] || MODEL="unknown"
127
+
128
+ # --- 5. Write the trailers ------------------------------------------------
129
+ # `--if-exists doNothing` makes this idempotent: `--amend`, a rebase that
130
+ # re-runs the hook, and a `-c` reuse all leave a single pair of trailers
131
+ # rather than stacking duplicates.
132
+ git interpret-trailers \
133
+ --in-place \
134
+ --if-exists doNothing \
135
+ --trailer "Switchroom-Agent: $AGENT" \
136
+ --trailer "Switchroom-Model: $MODEL" \
137
+ "$MSG_FILE" 2>/dev/null || true
138
+
139
+ # COMMIT_SOURCE is unused today but is part of the hook contract; naming it
140
+ # keeps `set -u` honest and documents that we deliberately stamp every
141
+ # source (message, template, merge, squash, commit/amend).
142
+ : "${COMMIT_SOURCE}"
143
+
144
+ exit 0
@@ -11114,10 +11114,37 @@ var AgentMemorySchema = exports_external.object({
11114
11114
  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."),
11115
11115
  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 use 2 " + "against the fleet-deployed cap of 6."),
11116
11116
  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 use 1 against the " + "fleet-deployed cap of 6. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
11117
+ 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`."),
11118
+ 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."),
11117
11119
  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)."),
11118
11120
  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)."),
11119
11121
  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`."),
11120
11122
  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."),
11123
+ budget: exports_external.enum(["low", "mid", "high"]).optional().describe('How hard Hindsight searches. "low" (switchroom default) = ' + 'vector retrieval only, ~1-2s. "mid" adds the LLM rerank pass ' + "and measured ~5s of hook latency on real fleet turns — the " + "second-largest contributor to perceived dead air after model " + 'TTFT. "high" is thorough and slower still. 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."),
11124
+ 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."),
11125
+ 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."),
11126
+ 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."),
11127
+ roles: exports_external.array(exports_external.string().min(1)).min(1).optional().describe("Transcript roles the multi-turn composition may draw from. " + 'Default ["user", "assistant"]. Set ["user"] to compose ' + "the query from the human's words only — worth trying when an " + "agent's own verbose replies are dominating the query terms. No " + "effect while `context_turns` is 1."),
11128
+ prompt_preamble: exports_external.string().min(1).optional().describe("The banner rendered above injected memories. The agent reads " + "this line as the instruction for how to treat the block, so it " + "is a behaviour knob, not cosmetics. Default tells the model to " + "prioritise recent memories on conflict and ignore irrelevant " + "ones. Override to tighten that framing for a specialised agent."),
11129
+ tags: exports_external.array(exports_external.string().min(1)).optional().describe("Restrict recall to memories carrying these tags. Default [] = " + "no filter (match everything). This is a HARD filter applied " + "server-side — a memory without the tags cannot surface at any " + "score — so it is for a genuinely scoped agent, not for " + "ranking. Use `tag_weights` when you want a preference rather " + "than an exclusion."),
11130
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional().describe('How `tags` combine. "any" (default) = at least one; ' + '"all" = every tag. The `_strict` forms additionally require ' + "the memory to actually carry the tags rather than merely rank " + "for them. No effect while `tags` and `tag_groups` are empty."),
11131
+ tag_groups: exports_external.union([
11132
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
11133
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
11134
+ ]).optional().describe("Tag filtering with grouping — either an OR-of-ANDs list " + '([["a","b"],["c"]] = (a AND b) OR c) or a named ' + "{group: [tags]} map. Default unset (no grouping). Use when a " + "flat `tags` + `tags_match` cannot express the scope you need."),
11135
+ tag_weights: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional().describe("Per-tag multipliers applied to `scores.final` just before the " + "final sort — a DEMOTION/PROMOTION, never a drop, so a " + "down-weighted memory still surfaces when it is the only " + "relevant hit. MERGED over switchroom's seed " + '({"sidechain": 0.8}, which ranks delegated sub-agent ' + "process-memories just under first-party ones), so setting one " + "unrelated weight does not silently undo it; pass " + "`sidechain: 1.0` to neutralise the seed. Reach for this when " + "recall_log shows one class of memory crowding the block."),
11136
+ additional_bank_filters: exports_external.record(exports_external.string(), exports_external.object({
11137
+ tags: exports_external.array(exports_external.string().min(1)).optional(),
11138
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional(),
11139
+ tag_groups: exports_external.union([
11140
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
11141
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
11142
+ ]).optional()
11143
+ }).strict()).optional().describe("Per-bank overrides of the tag filters above, keyed by bank id " + "(applies to `additional_banks` AND to sender banks). Default " + "{} = every extra bank inherits the global filters. Use it to " + "scope a shared bank — e.g. recall only `profile`-tagged " + "memories from the operator's profile bank while leaving the " + "agent's own bank unfiltered."),
11144
+ transcript_fallback: exports_external.boolean().optional().describe("When every bank returns zero results AND no bank hit its " + "deadline, grep the current session's transcript tail for turns " + "matching the query and inject them as a clearly-labelled " + "lower-confidence block. Default true — it covers the window " + "between an abrupt kill and the next boot reconciliation, where " + "the fact layer was never told about the lost turns. Set false " + "if you never want un-consolidated transcript text in context."),
11145
+ transcript_tail_bytes: exports_external.number().int().min(0).optional().describe("Bytes of the session transcript read from the tail for the " + "multi-turn query composition. Default 262144 (256 KiB), which " + "keeps the per-turn read O(1) on a session log that can grow to " + "many MB. 0 = read the whole file (the pre-bound behaviour, and " + "the rollback lever if a composition ever needs older turns)."),
11146
+ max_query_chars: exports_external.number().int().min(1).optional().describe("Character bound on the composed recall query, applied before " + "`query_max_tokens` shapes it. Default 800. Truncation preserves " + "the latest turn and drops the oldest context first. Lower it " + "for an agent whose turns are long pasted payloads."),
11147
+ parallel: exports_external.boolean().optional().describe("Run the directives fetch and every bank recall concurrently " + "under one shared deadline, so total latency is the SLOWEST slot " + "rather than their SUM. Default true. false restores the serial " + "path — the rollback lever if the parallel path ever misbehaves; " + "expect multi-bank recall latency to add up."),
11121
11148
  topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) → soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: …' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
11122
11149
  }).optional().describe("Auto-recall tuning knobs"),
11123
11150
  retain: exports_external.object({
@@ -11330,6 +11357,7 @@ var HindsightPerOpLlmSchema = exports_external.object({
11330
11357
  context_window: exports_external.number().int().positive().optional().describe("Context window (tokens) of the backend serving THIS op. NOT an " + "upstream env var — switchroom derives the op's token budget " + "(consolidation batch size / max-completion caps / reflect " + "max-context cap) from it so a single call can never overflow the " + "window. Absent → inherit " + "`hindsight.llm.context_window`, else a per-provider default " + "(conservative for non-`claude-code` providers, which usually mean " + "a local llama.cpp/Ollama slot; a self-hosted `base_url` — loopback, " + "RFC1918, `.local`/`.internal` — forces the conservative default too, " + "regardless of the provider NAME, since the endpoint is where the " + "traffic actually terminates). All three lanes (`retain`, " + "`reflect`, `consolidation`) are budgeted independently.")
11331
11358
  }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback — switchroom emits only the vars set.");
11332
11359
  var HindsightConfigSchema = exports_external.object({
11360
+ gpu: exports_external.boolean().optional().describe("Force GPU passthrough for the hindsight container on (`true`) or off " + "(`false`), overriding host autodetection in BOTH directions. Absent " + "(the default) → autodetect from the persisted host-capabilities " + "verdict (`~/.switchroom/host-capabilities.json`), which enables " + "`--gpus all` only when that file proves BOTH a GPU and the nvidia " + "container toolkit. Set `true` when that verdict is wrong or unreadable " + "and you know the host has a working toolkit — switchroom cannot verify " + "it for you, and `docker run --gpus all` hard-fails container create on " + "a host without one. Set `false` to pin the container to CPU on a GPU " + "host. This is also the declarative opt-out for the recreate-time GPU " + "drop guard (`switchroom memory setup --recreate` refuses to silently " + "turn a GPU container into a CPU one). `--gpu`/`--no-gpu` on `memory " + "setup` override this for a single run."),
11333
11361
  llm: exports_external.object({
11334
11362
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
11335
11363
  model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
@@ -11481,6 +11509,33 @@ var profileFields = {
11481
11509
  request_timeout_seconds: exports_external.number().int().min(1).optional(),
11482
11510
  own_bank_min_slots: exports_external.number().int().min(0).optional(),
11483
11511
  additional_bank_min_slots: exports_external.number().int().min(0).optional(),
11512
+ min_score: exports_external.number().min(0).optional(),
11513
+ min_score_scope: exports_external.enum(["degraded", "all"]).optional(),
11514
+ budget: exports_external.enum(["low", "mid", "high"]).optional(),
11515
+ max_tokens: exports_external.number().int().min(1).optional(),
11516
+ prefer_observations: exports_external.boolean().optional(),
11517
+ context_turns: exports_external.number().int().min(1).optional(),
11518
+ roles: exports_external.array(exports_external.string().min(1)).min(1).optional(),
11519
+ prompt_preamble: exports_external.string().min(1).optional(),
11520
+ tags: exports_external.array(exports_external.string().min(1)).optional(),
11521
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional(),
11522
+ tag_groups: exports_external.union([
11523
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
11524
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
11525
+ ]).optional(),
11526
+ tag_weights: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
11527
+ additional_bank_filters: exports_external.record(exports_external.string(), exports_external.object({
11528
+ tags: exports_external.array(exports_external.string().min(1)).optional(),
11529
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional(),
11530
+ tag_groups: exports_external.union([
11531
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
11532
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
11533
+ ]).optional()
11534
+ }).strict()).optional(),
11535
+ transcript_fallback: exports_external.boolean().optional(),
11536
+ transcript_tail_bytes: exports_external.number().int().min(0).optional(),
11537
+ max_query_chars: exports_external.number().int().min(1).optional(),
11538
+ parallel: exports_external.boolean().optional(),
11484
11539
  additional_banks: exports_external.array(exports_external.string()).optional(),
11485
11540
  sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
11486
11541
  }).optional()
@@ -16756,10 +16756,37 @@ var AgentMemorySchema = exports_external.object({
16756
16756
  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."),
16757
16757
  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 use 2 " + "against the fleet-deployed cap of 6."),
16758
16758
  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 use 1 against the " + "fleet-deployed cap of 6. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
16759
+ 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`."),
16760
+ 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."),
16759
16761
  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)."),
16760
16762
  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)."),
16761
16763
  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`."),
16762
16764
  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."),
16765
+ budget: exports_external.enum(["low", "mid", "high"]).optional().describe('How hard Hindsight searches. "low" (switchroom default) = ' + 'vector retrieval only, ~1-2s. "mid" adds the LLM rerank pass ' + "and measured ~5s of hook latency on real fleet turns — the " + "second-largest contributor to perceived dead air after model " + 'TTFT. "high" is thorough and slower still. 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."),
16766
+ 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."),
16767
+ 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."),
16768
+ 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."),
16769
+ roles: exports_external.array(exports_external.string().min(1)).min(1).optional().describe("Transcript roles the multi-turn composition may draw from. " + 'Default ["user", "assistant"]. Set ["user"] to compose ' + "the query from the human's words only — worth trying when an " + "agent's own verbose replies are dominating the query terms. No " + "effect while `context_turns` is 1."),
16770
+ prompt_preamble: exports_external.string().min(1).optional().describe("The banner rendered above injected memories. The agent reads " + "this line as the instruction for how to treat the block, so it " + "is a behaviour knob, not cosmetics. Default tells the model to " + "prioritise recent memories on conflict and ignore irrelevant " + "ones. Override to tighten that framing for a specialised agent."),
16771
+ tags: exports_external.array(exports_external.string().min(1)).optional().describe("Restrict recall to memories carrying these tags. Default [] = " + "no filter (match everything). This is a HARD filter applied " + "server-side — a memory without the tags cannot surface at any " + "score — so it is for a genuinely scoped agent, not for " + "ranking. Use `tag_weights` when you want a preference rather " + "than an exclusion."),
16772
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional().describe('How `tags` combine. "any" (default) = at least one; ' + '"all" = every tag. The `_strict` forms additionally require ' + "the memory to actually carry the tags rather than merely rank " + "for them. No effect while `tags` and `tag_groups` are empty."),
16773
+ tag_groups: exports_external.union([
16774
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
16775
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
16776
+ ]).optional().describe("Tag filtering with grouping — either an OR-of-ANDs list " + '([["a","b"],["c"]] = (a AND b) OR c) or a named ' + "{group: [tags]} map. Default unset (no grouping). Use when a " + "flat `tags` + `tags_match` cannot express the scope you need."),
16777
+ tag_weights: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional().describe("Per-tag multipliers applied to `scores.final` just before the " + "final sort — a DEMOTION/PROMOTION, never a drop, so a " + "down-weighted memory still surfaces when it is the only " + "relevant hit. MERGED over switchroom's seed " + '({"sidechain": 0.8}, which ranks delegated sub-agent ' + "process-memories just under first-party ones), so setting one " + "unrelated weight does not silently undo it; pass " + "`sidechain: 1.0` to neutralise the seed. Reach for this when " + "recall_log shows one class of memory crowding the block."),
16778
+ additional_bank_filters: exports_external.record(exports_external.string(), exports_external.object({
16779
+ tags: exports_external.array(exports_external.string().min(1)).optional(),
16780
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional(),
16781
+ tag_groups: exports_external.union([
16782
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
16783
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
16784
+ ]).optional()
16785
+ }).strict()).optional().describe("Per-bank overrides of the tag filters above, keyed by bank id " + "(applies to `additional_banks` AND to sender banks). Default " + "{} = every extra bank inherits the global filters. Use it to " + "scope a shared bank — e.g. recall only `profile`-tagged " + "memories from the operator's profile bank while leaving the " + "agent's own bank unfiltered."),
16786
+ transcript_fallback: exports_external.boolean().optional().describe("When every bank returns zero results AND no bank hit its " + "deadline, grep the current session's transcript tail for turns " + "matching the query and inject them as a clearly-labelled " + "lower-confidence block. Default true — it covers the window " + "between an abrupt kill and the next boot reconciliation, where " + "the fact layer was never told about the lost turns. Set false " + "if you never want un-consolidated transcript text in context."),
16787
+ transcript_tail_bytes: exports_external.number().int().min(0).optional().describe("Bytes of the session transcript read from the tail for the " + "multi-turn query composition. Default 262144 (256 KiB), which " + "keeps the per-turn read O(1) on a session log that can grow to " + "many MB. 0 = read the whole file (the pre-bound behaviour, and " + "the rollback lever if a composition ever needs older turns)."),
16788
+ max_query_chars: exports_external.number().int().min(1).optional().describe("Character bound on the composed recall query, applied before " + "`query_max_tokens` shapes it. Default 800. Truncation preserves " + "the latest turn and drops the oldest context first. Lower it " + "for an agent whose turns are long pasted payloads."),
16789
+ parallel: exports_external.boolean().optional().describe("Run the directives fetch and every bank recall concurrently " + "under one shared deadline, so total latency is the SLOWEST slot " + "rather than their SUM. Default true. false restores the serial " + "path — the rollback lever if the parallel path ever misbehaves; " + "expect multi-bank recall latency to add up."),
16763
16790
  topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) → soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: …' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
16764
16791
  }).optional().describe("Auto-recall tuning knobs"),
16765
16792
  retain: exports_external.object({
@@ -16972,6 +16999,7 @@ var HindsightPerOpLlmSchema = exports_external.object({
16972
16999
  context_window: exports_external.number().int().positive().optional().describe("Context window (tokens) of the backend serving THIS op. NOT an " + "upstream env var — switchroom derives the op's token budget " + "(consolidation batch size / max-completion caps / reflect " + "max-context cap) from it so a single call can never overflow the " + "window. Absent → inherit " + "`hindsight.llm.context_window`, else a per-provider default " + "(conservative for non-`claude-code` providers, which usually mean " + "a local llama.cpp/Ollama slot; a self-hosted `base_url` — loopback, " + "RFC1918, `.local`/`.internal` — forces the conservative default too, " + "regardless of the provider NAME, since the endpoint is where the " + "traffic actually terminates). All three lanes (`retain`, " + "`reflect`, `consolidation`) are budgeted independently.")
16973
17000
  }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback — switchroom emits only the vars set.");
16974
17001
  var HindsightConfigSchema = exports_external.object({
17002
+ gpu: exports_external.boolean().optional().describe("Force GPU passthrough for the hindsight container on (`true`) or off " + "(`false`), overriding host autodetection in BOTH directions. Absent " + "(the default) → autodetect from the persisted host-capabilities " + "verdict (`~/.switchroom/host-capabilities.json`), which enables " + "`--gpus all` only when that file proves BOTH a GPU and the nvidia " + "container toolkit. Set `true` when that verdict is wrong or unreadable " + "and you know the host has a working toolkit — switchroom cannot verify " + "it for you, and `docker run --gpus all` hard-fails container create on " + "a host without one. Set `false` to pin the container to CPU on a GPU " + "host. This is also the declarative opt-out for the recreate-time GPU " + "drop guard (`switchroom memory setup --recreate` refuses to silently " + "turn a GPU container into a CPU one). `--gpu`/`--no-gpu` on `memory " + "setup` override this for a single run."),
16975
17003
  llm: exports_external.object({
16976
17004
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
16977
17005
  model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
@@ -17123,6 +17151,33 @@ var profileFields = {
17123
17151
  request_timeout_seconds: exports_external.number().int().min(1).optional(),
17124
17152
  own_bank_min_slots: exports_external.number().int().min(0).optional(),
17125
17153
  additional_bank_min_slots: exports_external.number().int().min(0).optional(),
17154
+ min_score: exports_external.number().min(0).optional(),
17155
+ min_score_scope: exports_external.enum(["degraded", "all"]).optional(),
17156
+ budget: exports_external.enum(["low", "mid", "high"]).optional(),
17157
+ max_tokens: exports_external.number().int().min(1).optional(),
17158
+ prefer_observations: exports_external.boolean().optional(),
17159
+ context_turns: exports_external.number().int().min(1).optional(),
17160
+ roles: exports_external.array(exports_external.string().min(1)).min(1).optional(),
17161
+ prompt_preamble: exports_external.string().min(1).optional(),
17162
+ tags: exports_external.array(exports_external.string().min(1)).optional(),
17163
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional(),
17164
+ tag_groups: exports_external.union([
17165
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
17166
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
17167
+ ]).optional(),
17168
+ tag_weights: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
17169
+ additional_bank_filters: exports_external.record(exports_external.string(), exports_external.object({
17170
+ tags: exports_external.array(exports_external.string().min(1)).optional(),
17171
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional(),
17172
+ tag_groups: exports_external.union([
17173
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
17174
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
17175
+ ]).optional()
17176
+ }).strict()).optional(),
17177
+ transcript_fallback: exports_external.boolean().optional(),
17178
+ transcript_tail_bytes: exports_external.number().int().min(0).optional(),
17179
+ max_query_chars: exports_external.number().int().min(1).optional(),
17180
+ parallel: exports_external.boolean().optional(),
17126
17181
  additional_banks: exports_external.array(exports_external.string()).optional(),
17127
17182
  sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
17128
17183
  }).optional()
@@ -18413,6 +18468,9 @@ function assertPositive(value, label) {
18413
18468
  }
18414
18469
  }
18415
18470
 
18471
+ // src/setup/host-capabilities.ts
18472
+ var _warnedReads = new Set;
18473
+
18416
18474
  // src/setup/hindsight-pg-defaults.ts
18417
18475
  var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 8 * 1024;
18418
18476
  var HINDSIGHT_PG_APP_ANON_MIB = 2560;
@@ -18575,7 +18633,42 @@ var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
18575
18633
  var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
18576
18634
 
18577
18635
  // src/memory/hindsight.ts
18578
- var DEFAULT_RETAIN_MISSION = "Extract durable facts that will still be true and useful weeks from now: " + "user preferences and standing rules, ongoing projects and recurring " + "commitments, technical and architectural decisions with their rationale, " + "and people/tool relationships. A preference revealed by a request is " + "durable — record the preference (what the user likes, wants, or always " + `does), not the request itself.
18636
+ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and useful weeks from now: user preferences and standing rules, ongoing projects and recurring commitments, technical and architectural decisions with their rationale, and people/tool relationships. A preference revealed by a request is durable — record the preference (what the user likes, wants, or always does), not the request itself.
18637
+ ` + `
18638
+ ` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
18639
+ ` + `candidate a file path, a command/process/agent/session id, a temp directory, or
18640
+ ` + `the location where some output was written? If yes, drop it — it is transcript
18641
+ ` + `exhaust, not memory.
18642
+ ` + `
18643
+ ` + `NEVER extract:
18644
+ ` + `- Tool results verbatim or paraphrased. Concretely, never produce a fact whose
18645
+ ` + ` text resembles any of these: "File created successfully at /path/to/file",
18646
+ ` + ` "A background command with ID bctz4yskm is running, and its output will be
18647
+ ` + ` written to /tmp/...", "Async agent a745598ba84e71df1 was launched successfully
18648
+ ` + ` and is running in the background", "User executed a Bash command to sleep for
18649
+ ` + ` 200 seconds", "The assistant used grep to locate 'truncateSync' in src/foo.ts".
18650
+ ` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
18651
+ ` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
18652
+ ` + ` assistant used X to query Y", "ran a search", "sent the message").
18653
+ ` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
18654
+ ` + ` running) — retain the outcome only once the task completes or a decision is made.
18655
+ ` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
18656
+ ` + `- Slash commands the user typed and their effects (e.g. "User issued /clear to
18657
+ ` + ` reset assistant state").
18658
+ ` + `- Hindsight's own errors, retries, backlogs, or internal state — the memory
18659
+ ` + ` system's self-reports are not memories.
18660
+ ` + `- Restatements of the user's current request or the task in progress.
18661
+ ` + `- Transient state (unread counts, build status, what is running right now) unless
18662
+ ` + ` the fact is explicitly dated, in which case record it as a dated observation.
18663
+ ` + `- Greetings, acknowledgements, and routine operational chatter.
18664
+ ` + `
18665
+ ` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
18666
+ ` + "nothing durable remains, return an empty facts list.";
18667
+ var SUPERSEDED_RETAIN_MISSIONS = [
18668
+ "Extract technical decisions, architectural choices, user preferences, project context, and people/tool relationships. Ignore routine greetings and transient operational details.",
18669
+ "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.",
18670
+ "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) — only retain the outcome once a task " + "actually completes or a decision is made.",
18671
+ "Extract durable facts that will still be true and useful weeks from now: " + "user preferences and standing rules, ongoing projects and recurring " + "commitments, technical and architectural decisions with their rationale, " + "and people/tool relationships. A preference revealed by a request is " + "durable — record the preference (what the user likes, wants, or always " + `does), not the request itself.
18579
18672
 
18580
18673
  ` + `NEVER extract:
18581
18674
  ` + "- Agent tool-use traces or narration of what the assistant did (e.g. " + `"the assistant used X to query Y", "ran a search", "sent the message").
@@ -18586,11 +18679,7 @@ var DEFAULT_RETAIN_MISSION = "Extract durable facts that will still be true and
18586
18679
  ` + "- Transient state (unread counts, build status, what is running right now) " + "unless the fact is explicitly dated, in which case record it as a dated " + `observation.
18587
18680
  ` + `- Greetings, acknowledgements, and routine operational chatter.
18588
18681
 
18589
- ` + "If a candidate fact matches an exclusion, drop it rather than rewording " + "it. If nothing durable remains, return an empty facts list.";
18590
- var SUPERSEDED_RETAIN_MISSIONS = [
18591
- "Extract technical decisions, architectural choices, user preferences, project context, and people/tool relationships. Ignore routine greetings and transient operational details.",
18592
- "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.",
18593
- "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) — only retain the outcome once a task " + "actually completes or a decision is made."
18682
+ ` + "If a candidate fact matches an exclusion, drop it rather than rewording " + "it. If nothing durable remains, return an empty facts list."
18594
18683
  ];
18595
18684
  var PROFILE_MEMORY_DEFAULTS = {
18596
18685
  "health-coach": {
@@ -18974,6 +19063,27 @@ var OPERATOR_SOCKET_PATH = join3(homedir3(), ".switchroom", "broker-operator", "
18974
19063
  // src/vault/resolver.ts
18975
19064
  var materializedDirs = new Set;
18976
19065
 
19066
+ // src/setup/hindsight-recall-passthrough.ts
19067
+ var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
19068
+ 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:";
19069
+ var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
19070
+ budget: "low",
19071
+ maxTokens: 1024,
19072
+ preferObservations: true,
19073
+ contextTurns: 2,
19074
+ roles: ["user", "assistant"],
19075
+ promptPreamble: HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT,
19076
+ tags: [],
19077
+ tagsMatch: "any",
19078
+ tagGroups: {},
19079
+ tagWeights: HINDSIGHT_RECALL_TAG_WEIGHT_SEED,
19080
+ additionalBankFilters: {},
19081
+ transcriptFallback: true,
19082
+ transcriptTailBytes: 262144,
19083
+ maxQueryChars: 800,
19084
+ parallel: true
19085
+ });
19086
+
18977
19087
  // src/agents/scaffold.ts
18978
19088
  var REPO_ROOT = resolve5(import.meta.dirname, "../..");
18979
19089
  var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
@@ -11873,10 +11873,37 @@ var AgentMemorySchema = exports_external.object({
11873
11873
  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."),
11874
11874
  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 use 2 " + "against the fleet-deployed cap of 6."),
11875
11875
  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 use 1 against the " + "fleet-deployed cap of 6. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
11876
+ 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`."),
11877
+ 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."),
11876
11878
  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)."),
11877
11879
  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)."),
11878
11880
  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`."),
11879
11881
  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."),
11882
+ budget: exports_external.enum(["low", "mid", "high"]).optional().describe('How hard Hindsight searches. "low" (switchroom default) = ' + 'vector retrieval only, ~1-2s. "mid" adds the LLM rerank pass ' + "and measured ~5s of hook latency on real fleet turns \u2014 the " + "second-largest contributor to perceived dead air after model " + 'TTFT. "high" is thorough and slower still. 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."),
11883
+ 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."),
11884
+ 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."),
11885
+ 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."),
11886
+ roles: exports_external.array(exports_external.string().min(1)).min(1).optional().describe("Transcript roles the multi-turn composition may draw from. " + 'Default ["user", "assistant"]. Set ["user"] to compose ' + "the query from the human's words only \u2014 worth trying when an " + "agent's own verbose replies are dominating the query terms. No " + "effect while `context_turns` is 1."),
11887
+ prompt_preamble: exports_external.string().min(1).optional().describe("The banner rendered above injected memories. The agent reads " + "this line as the instruction for how to treat the block, so it " + "is a behaviour knob, not cosmetics. Default tells the model to " + "prioritise recent memories on conflict and ignore irrelevant " + "ones. Override to tighten that framing for a specialised agent."),
11888
+ tags: exports_external.array(exports_external.string().min(1)).optional().describe("Restrict recall to memories carrying these tags. Default [] = " + "no filter (match everything). This is a HARD filter applied " + "server-side \u2014 a memory without the tags cannot surface at any " + "score \u2014 so it is for a genuinely scoped agent, not for " + "ranking. Use `tag_weights` when you want a preference rather " + "than an exclusion."),
11889
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional().describe('How `tags` combine. "any" (default) = at least one; ' + '"all" = every tag. The `_strict` forms additionally require ' + "the memory to actually carry the tags rather than merely rank " + "for them. No effect while `tags` and `tag_groups` are empty."),
11890
+ tag_groups: exports_external.union([
11891
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
11892
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
11893
+ ]).optional().describe("Tag filtering with grouping \u2014 either an OR-of-ANDs list " + '([["a","b"],["c"]] = (a AND b) OR c) or a named ' + "{group: [tags]} map. Default unset (no grouping). Use when a " + "flat `tags` + `tags_match` cannot express the scope you need."),
11894
+ tag_weights: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional().describe("Per-tag multipliers applied to `scores.final` just before the " + "final sort \u2014 a DEMOTION/PROMOTION, never a drop, so a " + "down-weighted memory still surfaces when it is the only " + "relevant hit. MERGED over switchroom's seed " + '({"sidechain": 0.8}, which ranks delegated sub-agent ' + "process-memories just under first-party ones), so setting one " + "unrelated weight does not silently undo it; pass " + "`sidechain: 1.0` to neutralise the seed. Reach for this when " + "recall_log shows one class of memory crowding the block."),
11895
+ additional_bank_filters: exports_external.record(exports_external.string(), exports_external.object({
11896
+ tags: exports_external.array(exports_external.string().min(1)).optional(),
11897
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional(),
11898
+ tag_groups: exports_external.union([
11899
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
11900
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
11901
+ ]).optional()
11902
+ }).strict()).optional().describe("Per-bank overrides of the tag filters above, keyed by bank id " + "(applies to `additional_banks` AND to sender banks). Default " + "{} = every extra bank inherits the global filters. Use it to " + "scope a shared bank \u2014 e.g. recall only `profile`-tagged " + "memories from the operator's profile bank while leaving the " + "agent's own bank unfiltered."),
11903
+ transcript_fallback: exports_external.boolean().optional().describe("When every bank returns zero results AND no bank hit its " + "deadline, grep the current session's transcript tail for turns " + "matching the query and inject them as a clearly-labelled " + "lower-confidence block. Default true \u2014 it covers the window " + "between an abrupt kill and the next boot reconciliation, where " + "the fact layer was never told about the lost turns. Set false " + "if you never want un-consolidated transcript text in context."),
11904
+ transcript_tail_bytes: exports_external.number().int().min(0).optional().describe("Bytes of the session transcript read from the tail for the " + "multi-turn query composition. Default 262144 (256 KiB), which " + "keeps the per-turn read O(1) on a session log that can grow to " + "many MB. 0 = read the whole file (the pre-bound behaviour, and " + "the rollback lever if a composition ever needs older turns)."),
11905
+ max_query_chars: exports_external.number().int().min(1).optional().describe("Character bound on the composed recall query, applied before " + "`query_max_tokens` shapes it. Default 800. Truncation preserves " + "the latest turn and drops the oldest context first. Lower it " + "for an agent whose turns are long pasted payloads."),
11906
+ parallel: exports_external.boolean().optional().describe("Run the directives fetch and every bank recall concurrently " + "under one shared deadline, so total latency is the SLOWEST slot " + "rather than their SUM. Default true. false restores the serial " + "path \u2014 the rollback lever if the parallel path ever misbehaves; " + "expect multi-bank recall latency to add up."),
11880
11907
  topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) \u2192 soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: \u2026' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
11881
11908
  }).optional().describe("Auto-recall tuning knobs"),
11882
11909
  retain: exports_external.object({
@@ -12089,6 +12116,7 @@ var HindsightPerOpLlmSchema = exports_external.object({
12089
12116
  context_window: exports_external.number().int().positive().optional().describe("Context window (tokens) of the backend serving THIS op. NOT an " + "upstream env var \u2014 switchroom derives the op's token budget " + "(consolidation batch size / max-completion caps / reflect " + "max-context cap) from it so a single call can never overflow the " + "window. Absent \u2192 inherit " + "`hindsight.llm.context_window`, else a per-provider default " + "(conservative for non-`claude-code` providers, which usually mean " + "a local llama.cpp/Ollama slot; a self-hosted `base_url` \u2014 loopback, " + "RFC1918, `.local`/`.internal` \u2014 forces the conservative default too, " + "regardless of the provider NAME, since the endpoint is where the " + "traffic actually terminates). All three lanes (`retain`, " + "`reflect`, `consolidation`) are budgeted independently.")
12090
12117
  }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback \u2014 switchroom emits only the vars set.");
12091
12118
  var HindsightConfigSchema = exports_external.object({
12119
+ gpu: exports_external.boolean().optional().describe("Force GPU passthrough for the hindsight container on (`true`) or off " + "(`false`), overriding host autodetection in BOTH directions. Absent " + "(the default) \u2192 autodetect from the persisted host-capabilities " + "verdict (`~/.switchroom/host-capabilities.json`), which enables " + "`--gpus all` only when that file proves BOTH a GPU and the nvidia " + "container toolkit. Set `true` when that verdict is wrong or unreadable " + "and you know the host has a working toolkit \u2014 switchroom cannot verify " + "it for you, and `docker run --gpus all` hard-fails container create on " + "a host without one. Set `false` to pin the container to CPU on a GPU " + "host. This is also the declarative opt-out for the recreate-time GPU " + "drop guard (`switchroom memory setup --recreate` refuses to silently " + "turn a GPU container into a CPU one). `--gpu`/`--no-gpu` on `memory " + "setup` override this for a single run."),
12092
12120
  llm: exports_external.object({
12093
12121
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
12094
12122
  model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
@@ -12240,6 +12268,33 @@ var profileFields = {
12240
12268
  request_timeout_seconds: exports_external.number().int().min(1).optional(),
12241
12269
  own_bank_min_slots: exports_external.number().int().min(0).optional(),
12242
12270
  additional_bank_min_slots: exports_external.number().int().min(0).optional(),
12271
+ min_score: exports_external.number().min(0).optional(),
12272
+ min_score_scope: exports_external.enum(["degraded", "all"]).optional(),
12273
+ budget: exports_external.enum(["low", "mid", "high"]).optional(),
12274
+ max_tokens: exports_external.number().int().min(1).optional(),
12275
+ prefer_observations: exports_external.boolean().optional(),
12276
+ context_turns: exports_external.number().int().min(1).optional(),
12277
+ roles: exports_external.array(exports_external.string().min(1)).min(1).optional(),
12278
+ prompt_preamble: exports_external.string().min(1).optional(),
12279
+ tags: exports_external.array(exports_external.string().min(1)).optional(),
12280
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional(),
12281
+ tag_groups: exports_external.union([
12282
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
12283
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
12284
+ ]).optional(),
12285
+ tag_weights: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
12286
+ additional_bank_filters: exports_external.record(exports_external.string(), exports_external.object({
12287
+ tags: exports_external.array(exports_external.string().min(1)).optional(),
12288
+ tags_match: exports_external.enum(["any", "all", "any_strict", "all_strict"]).optional(),
12289
+ tag_groups: exports_external.union([
12290
+ exports_external.array(exports_external.array(exports_external.string().min(1))),
12291
+ exports_external.record(exports_external.string(), exports_external.array(exports_external.string().min(1)))
12292
+ ]).optional()
12293
+ }).strict()).optional(),
12294
+ transcript_fallback: exports_external.boolean().optional(),
12295
+ transcript_tail_bytes: exports_external.number().int().min(0).optional(),
12296
+ max_query_chars: exports_external.number().int().min(1).optional(),
12297
+ parallel: exports_external.boolean().optional(),
12243
12298
  additional_banks: exports_external.array(exports_external.string()).optional(),
12244
12299
  sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
12245
12300
  }).optional()