switchroom 0.15.42 → 0.15.44
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 +37 -1
- package/dist/auth-broker/index.js +37 -1
- package/dist/cli/notion-write-pretool.mjs +37 -1
- package/dist/cli/switchroom.js +194 -9
- package/dist/host-control/main.js +37 -1
- package/dist/vault/approvals/kernel-server.js +38 -2
- package/dist/vault/broker/server.js +38 -2
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +6 -0
- package/telegram-plugin/dist/gateway/gateway.js +43 -7
- package/vendor/hindsight-memory/scripts/lib/gateway_ipc.py +29 -0
- package/vendor/hindsight-memory/scripts/recall.py +65 -2
- package/vendor/hindsight-memory/scripts/tests/test_sender_routing.py +127 -0
|
@@ -11077,6 +11077,8 @@ var AgentMemorySchema = exports_external.object({
|
|
|
11077
11077
|
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."),
|
|
11078
11078
|
min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0–1.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default — " + "current behaviour). Try 0.10–0.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
|
|
11079
11079
|
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)."),
|
|
11080
|
+
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 an 8s timeout 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)."),
|
|
11081
|
+
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`."),
|
|
11080
11082
|
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."),
|
|
11081
11083
|
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).")
|
|
11082
11084
|
}).optional().describe("Auto-recall tuning knobs")
|
|
@@ -11290,8 +11292,12 @@ var ReleaseBlock = exports_external.object({
|
|
|
11290
11292
|
message: "release.channel and release.pin are mutually exclusive"
|
|
11291
11293
|
});
|
|
11292
11294
|
var NetworkIsolationSchema = exports_external.enum(["host", "strict"]).optional().describe("Container network mode (sec WS6-F1 #1390 / feature #1413). " + "'host' (DEFAULT when unset): `network_mode: host` — the agent " + "shares the host network stack; hindsight 127.0.0.1:18888 and " + "operator-LAN devices are reachable, but there is NO network " + "isolation from sibling agents or host services (the documented, " + "deliberate shared-host tradeoff). 'strict': the agent joins its " + "OWN dedicated docker bridge network instead — it cannot reach " + "sibling agents; host services are reached via " + "`host.docker.internal`. OPT-IN: validate hindsight / operator-" + "LAN / cron / boot-self-test paths for your deployment before " + "adopting fleet-wide (default-flip is deferred to that validation " + "cycle, #1413). Cascades override (agent → profile → defaults).");
|
|
11295
|
+
var servesField = exports_external.array(exports_external.string()).optional().describe("Users (keys in the top-level `users:` block) this agent works for. When " + "a served user messages this agent, their profile_bank is recalled " + "(speaker routing → memory.recall.sender_banks). Unions with any explicit " + "memory.recall.sender_banks. NOTE: this does not yet generate access " + "(allowFrom) — pair agent access as today; allowFrom generation is a " + "later phase.");
|
|
11296
|
+
var knowsField = exports_external.array(exports_external.string()).optional().describe("Users or banks this agent always knows as subjects — recalled and " + "recall-ranked even when that person is not the speaker (→ " + "memory.recall.additional_banks). A `users:` key resolves to that user's " + "profile_bank; any other string is used as a raw bank name (e.g. a `kids` " + "profile bank with no Telegram identity). Unions with any explicit " + "memory.recall.additional_banks.");
|
|
11293
11297
|
var profileFields = {
|
|
11294
11298
|
extends: exports_external.string().optional(),
|
|
11299
|
+
serves: servesField,
|
|
11300
|
+
knows: knowsField,
|
|
11295
11301
|
bot_token: exports_external.string().optional(),
|
|
11296
11302
|
release: ReleaseBlock.optional().describe("Release-channel pin / pointer. Either `channel` (dev|rc|latest) or " + "`pin` (sha-<hex>|v<semver>) — mutually exclusive. Per-agent value " + "REPLACES the root entirely (no field merge)."),
|
|
11297
11303
|
timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC' " + "(three-letter aliases like EST/PST and bare offsets like UTC+10 are not accepted)").optional().describe("IANA timezone name (e.g. 'Australia/Melbourne', 'America/New_York', " + "'UTC'). Used to generate the per-turn local-time hint the agent's " + "UserPromptSubmit timezone hook emits, and baked into the agent " + "container's `environment.TZ` in compose so subprocess `date`/" + "`Date.now()` are correct. If unset at every cascade layer, switchroom " + "auto-detects from /etc/timezone and warns on `reconcile` when the " + "detected zone is UTC."),
|
|
@@ -11312,7 +11318,9 @@ var profileFields = {
|
|
|
11312
11318
|
recall: exports_external.object({
|
|
11313
11319
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
11314
11320
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
11315
|
-
min_overlap: exports_external.number().min(0).max(1).optional()
|
|
11321
|
+
min_overlap: exports_external.number().min(0).max(1).optional(),
|
|
11322
|
+
additional_banks: exports_external.array(exports_external.string()).optional(),
|
|
11323
|
+
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
11316
11324
|
}).optional()
|
|
11317
11325
|
}).optional(),
|
|
11318
11326
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
@@ -11357,6 +11365,8 @@ var { extends: _omitExtends, ...defaultsFields } = profileFields;
|
|
|
11357
11365
|
var AgentDefaultsSchema = exports_external.object(defaultsFields).optional();
|
|
11358
11366
|
var AgentSchema = exports_external.object({
|
|
11359
11367
|
extends: exports_external.string().optional().describe("Name of a profile to inherit from (e.g., 'coding', 'health-coach'). " + "Profiles may be defined inline under switchroom.yaml `profiles:` or as a " + "filesystem directory `profiles/<name>/`. Defaults to DEFAULT_PROFILE " + "('default') when unset."),
|
|
11368
|
+
serves: servesField,
|
|
11369
|
+
knows: knowsField,
|
|
11360
11370
|
bot_token: exports_external.string().optional().describe("Per-agent Telegram bot token or vault reference (overrides global telegram.bot_token)"),
|
|
11361
11371
|
release: ReleaseBlock.optional().describe("Per-agent release-channel pin / pointer. REPLACES the root " + "`release` block entirely (no field merge) — a pinned agent does " + "not inherit the fleet channel, and vice versa."),
|
|
11362
11372
|
bot_username: exports_external.string().optional().describe("Per-agent Telegram bot username (without leading @) when it doesn't " + "contain the agent slug. Replaces the default 'username includes slug' " + "preflight check with an exact (case-insensitive) match. Use when an " + "agent and its bot have intentionally divergent names (e.g. agent " + "'lawgpt' paired with bot '@meken_law_bot')."),
|
|
@@ -11530,6 +11540,11 @@ var CronEgressSchema = exports_external.object({
|
|
|
11530
11540
|
var CronConfigSchema = exports_external.object({
|
|
11531
11541
|
egress: CronEgressSchema.optional().describe("SSRF/exfil fence for http-diff polls.")
|
|
11532
11542
|
});
|
|
11543
|
+
var UserSchema = exports_external.object({
|
|
11544
|
+
name: exports_external.string().optional().describe("Display name for the user."),
|
|
11545
|
+
telegram_ids: exports_external.array(exports_external.string()).min(1).describe("Telegram username(s) and/or numeric user id(s) identifying this user " + "(a leading @ is optional). Matched against the message sender for " + "per-speaker memory routing."),
|
|
11546
|
+
profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`).")
|
|
11547
|
+
});
|
|
11533
11548
|
var SwitchroomConfigSchema = exports_external.object({
|
|
11534
11549
|
switchroom: exports_external.object({
|
|
11535
11550
|
version: exports_external.literal(1).describe("Config schema version"),
|
|
@@ -11576,10 +11591,31 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
11576
11591
|
})).optional().describe("RFC #1873: per-Microsoft-account ACL. Maps account email → list of " + "agents permitted to use that account's broker credentials. Written " + "by `switchroom auth microsoft enable|disable`; read by the broker " + "on get-credentials with provider=microsoft."),
|
|
11577
11592
|
defaults: AgentDefaultsSchema.describe("Implicit bottom-of-cascade profile applied to every agent before " + "per-agent config and `extends:` resolution. Tools, mcp_servers, and " + "schedule are unioned/concatenated; scalars and nested objects are " + "shallow-merged with per-agent values winning."),
|
|
11578
11593
|
profiles: exports_external.record(exports_external.string(), ProfileSchema).optional().describe("Named profile definitions. Agents reference via `extends: <name>`. " + "Inline profiles declared here take priority over filesystem " + "profiles/<name>/ directories when both exist."),
|
|
11594
|
+
users: exports_external.record(exports_external.string(), UserSchema).optional().describe("Trusted users the fleet serves — each a Telegram identity plus a " + "memory profile bank. Assigned to agents via `serves` / `knows`. The " + "operator's own trusted people (single-tenant), not multi-tenant. See " + "reference/rfcs/user-concept.md."),
|
|
11579
11595
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
11580
11596
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
11581
11597
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
11582
11598
|
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (§6.1). Required to enable any http-diff poll; not agent-writable.")
|
|
11599
|
+
}).superRefine((cfg, ctx) => {
|
|
11600
|
+
const userKeys = new Set(Object.keys(cfg.users ?? {}));
|
|
11601
|
+
const checkServes = (serves, path) => {
|
|
11602
|
+
(serves ?? []).forEach((s, i) => {
|
|
11603
|
+
if (!userKeys.has(s)) {
|
|
11604
|
+
ctx.addIssue({
|
|
11605
|
+
code: exports_external.ZodIssueCode.custom,
|
|
11606
|
+
message: `serves references unknown user "${s}" — add it to the top-level ` + "`users:` block (or did you mean `knows` for a raw bank name?)",
|
|
11607
|
+
path: [...path, i]
|
|
11608
|
+
});
|
|
11609
|
+
}
|
|
11610
|
+
});
|
|
11611
|
+
};
|
|
11612
|
+
checkServes(cfg.defaults?.serves, ["defaults", "serves"]);
|
|
11613
|
+
for (const [name, p] of Object.entries(cfg.profiles ?? {})) {
|
|
11614
|
+
checkServes(p.serves, ["profiles", name, "serves"]);
|
|
11615
|
+
}
|
|
11616
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
11617
|
+
checkServes(a.serves, ["agents", name, "serves"]);
|
|
11618
|
+
}
|
|
11583
11619
|
});
|
|
11584
11620
|
|
|
11585
11621
|
// src/config/paths.ts
|
|
@@ -11077,6 +11077,8 @@ var AgentMemorySchema = exports_external.object({
|
|
|
11077
11077
|
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."),
|
|
11078
11078
|
min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0–1.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default — " + "current behaviour). Try 0.10–0.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
|
|
11079
11079
|
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)."),
|
|
11080
|
+
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 an 8s timeout 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)."),
|
|
11081
|
+
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`."),
|
|
11080
11082
|
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."),
|
|
11081
11083
|
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).")
|
|
11082
11084
|
}).optional().describe("Auto-recall tuning knobs")
|
|
@@ -11290,8 +11292,12 @@ var ReleaseBlock = exports_external.object({
|
|
|
11290
11292
|
message: "release.channel and release.pin are mutually exclusive"
|
|
11291
11293
|
});
|
|
11292
11294
|
var NetworkIsolationSchema = exports_external.enum(["host", "strict"]).optional().describe("Container network mode (sec WS6-F1 #1390 / feature #1413). " + "'host' (DEFAULT when unset): `network_mode: host` — the agent " + "shares the host network stack; hindsight 127.0.0.1:18888 and " + "operator-LAN devices are reachable, but there is NO network " + "isolation from sibling agents or host services (the documented, " + "deliberate shared-host tradeoff). 'strict': the agent joins its " + "OWN dedicated docker bridge network instead — it cannot reach " + "sibling agents; host services are reached via " + "`host.docker.internal`. OPT-IN: validate hindsight / operator-" + "LAN / cron / boot-self-test paths for your deployment before " + "adopting fleet-wide (default-flip is deferred to that validation " + "cycle, #1413). Cascades override (agent → profile → defaults).");
|
|
11295
|
+
var servesField = exports_external.array(exports_external.string()).optional().describe("Users (keys in the top-level `users:` block) this agent works for. When " + "a served user messages this agent, their profile_bank is recalled " + "(speaker routing → memory.recall.sender_banks). Unions with any explicit " + "memory.recall.sender_banks. NOTE: this does not yet generate access " + "(allowFrom) — pair agent access as today; allowFrom generation is a " + "later phase.");
|
|
11296
|
+
var knowsField = exports_external.array(exports_external.string()).optional().describe("Users or banks this agent always knows as subjects — recalled and " + "recall-ranked even when that person is not the speaker (→ " + "memory.recall.additional_banks). A `users:` key resolves to that user's " + "profile_bank; any other string is used as a raw bank name (e.g. a `kids` " + "profile bank with no Telegram identity). Unions with any explicit " + "memory.recall.additional_banks.");
|
|
11293
11297
|
var profileFields = {
|
|
11294
11298
|
extends: exports_external.string().optional(),
|
|
11299
|
+
serves: servesField,
|
|
11300
|
+
knows: knowsField,
|
|
11295
11301
|
bot_token: exports_external.string().optional(),
|
|
11296
11302
|
release: ReleaseBlock.optional().describe("Release-channel pin / pointer. Either `channel` (dev|rc|latest) or " + "`pin` (sha-<hex>|v<semver>) — mutually exclusive. Per-agent value " + "REPLACES the root entirely (no field merge)."),
|
|
11297
11303
|
timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC' " + "(three-letter aliases like EST/PST and bare offsets like UTC+10 are not accepted)").optional().describe("IANA timezone name (e.g. 'Australia/Melbourne', 'America/New_York', " + "'UTC'). Used to generate the per-turn local-time hint the agent's " + "UserPromptSubmit timezone hook emits, and baked into the agent " + "container's `environment.TZ` in compose so subprocess `date`/" + "`Date.now()` are correct. If unset at every cascade layer, switchroom " + "auto-detects from /etc/timezone and warns on `reconcile` when the " + "detected zone is UTC."),
|
|
@@ -11312,7 +11318,9 @@ var profileFields = {
|
|
|
11312
11318
|
recall: exports_external.object({
|
|
11313
11319
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
11314
11320
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
11315
|
-
min_overlap: exports_external.number().min(0).max(1).optional()
|
|
11321
|
+
min_overlap: exports_external.number().min(0).max(1).optional(),
|
|
11322
|
+
additional_banks: exports_external.array(exports_external.string()).optional(),
|
|
11323
|
+
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
11316
11324
|
}).optional()
|
|
11317
11325
|
}).optional(),
|
|
11318
11326
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
@@ -11357,6 +11365,8 @@ var { extends: _omitExtends, ...defaultsFields } = profileFields;
|
|
|
11357
11365
|
var AgentDefaultsSchema = exports_external.object(defaultsFields).optional();
|
|
11358
11366
|
var AgentSchema = exports_external.object({
|
|
11359
11367
|
extends: exports_external.string().optional().describe("Name of a profile to inherit from (e.g., 'coding', 'health-coach'). " + "Profiles may be defined inline under switchroom.yaml `profiles:` or as a " + "filesystem directory `profiles/<name>/`. Defaults to DEFAULT_PROFILE " + "('default') when unset."),
|
|
11368
|
+
serves: servesField,
|
|
11369
|
+
knows: knowsField,
|
|
11360
11370
|
bot_token: exports_external.string().optional().describe("Per-agent Telegram bot token or vault reference (overrides global telegram.bot_token)"),
|
|
11361
11371
|
release: ReleaseBlock.optional().describe("Per-agent release-channel pin / pointer. REPLACES the root " + "`release` block entirely (no field merge) — a pinned agent does " + "not inherit the fleet channel, and vice versa."),
|
|
11362
11372
|
bot_username: exports_external.string().optional().describe("Per-agent Telegram bot username (without leading @) when it doesn't " + "contain the agent slug. Replaces the default 'username includes slug' " + "preflight check with an exact (case-insensitive) match. Use when an " + "agent and its bot have intentionally divergent names (e.g. agent " + "'lawgpt' paired with bot '@meken_law_bot')."),
|
|
@@ -11530,6 +11540,11 @@ var CronEgressSchema = exports_external.object({
|
|
|
11530
11540
|
var CronConfigSchema = exports_external.object({
|
|
11531
11541
|
egress: CronEgressSchema.optional().describe("SSRF/exfil fence for http-diff polls.")
|
|
11532
11542
|
});
|
|
11543
|
+
var UserSchema = exports_external.object({
|
|
11544
|
+
name: exports_external.string().optional().describe("Display name for the user."),
|
|
11545
|
+
telegram_ids: exports_external.array(exports_external.string()).min(1).describe("Telegram username(s) and/or numeric user id(s) identifying this user " + "(a leading @ is optional). Matched against the message sender for " + "per-speaker memory routing."),
|
|
11546
|
+
profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`).")
|
|
11547
|
+
});
|
|
11533
11548
|
var SwitchroomConfigSchema = exports_external.object({
|
|
11534
11549
|
switchroom: exports_external.object({
|
|
11535
11550
|
version: exports_external.literal(1).describe("Config schema version"),
|
|
@@ -11576,10 +11591,31 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
11576
11591
|
})).optional().describe("RFC #1873: per-Microsoft-account ACL. Maps account email → list of " + "agents permitted to use that account's broker credentials. Written " + "by `switchroom auth microsoft enable|disable`; read by the broker " + "on get-credentials with provider=microsoft."),
|
|
11577
11592
|
defaults: AgentDefaultsSchema.describe("Implicit bottom-of-cascade profile applied to every agent before " + "per-agent config and `extends:` resolution. Tools, mcp_servers, and " + "schedule are unioned/concatenated; scalars and nested objects are " + "shallow-merged with per-agent values winning."),
|
|
11578
11593
|
profiles: exports_external.record(exports_external.string(), ProfileSchema).optional().describe("Named profile definitions. Agents reference via `extends: <name>`. " + "Inline profiles declared here take priority over filesystem " + "profiles/<name>/ directories when both exist."),
|
|
11594
|
+
users: exports_external.record(exports_external.string(), UserSchema).optional().describe("Trusted users the fleet serves — each a Telegram identity plus a " + "memory profile bank. Assigned to agents via `serves` / `knows`. The " + "operator's own trusted people (single-tenant), not multi-tenant. See " + "reference/rfcs/user-concept.md."),
|
|
11579
11595
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
11580
11596
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
11581
11597
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
11582
11598
|
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (§6.1). Required to enable any http-diff poll; not agent-writable.")
|
|
11599
|
+
}).superRefine((cfg, ctx) => {
|
|
11600
|
+
const userKeys = new Set(Object.keys(cfg.users ?? {}));
|
|
11601
|
+
const checkServes = (serves, path) => {
|
|
11602
|
+
(serves ?? []).forEach((s, i) => {
|
|
11603
|
+
if (!userKeys.has(s)) {
|
|
11604
|
+
ctx.addIssue({
|
|
11605
|
+
code: exports_external.ZodIssueCode.custom,
|
|
11606
|
+
message: `serves references unknown user "${s}" — add it to the top-level ` + "`users:` block (or did you mean `knows` for a raw bank name?)",
|
|
11607
|
+
path: [...path, i]
|
|
11608
|
+
});
|
|
11609
|
+
}
|
|
11610
|
+
});
|
|
11611
|
+
};
|
|
11612
|
+
checkServes(cfg.defaults?.serves, ["defaults", "serves"]);
|
|
11613
|
+
for (const [name, p] of Object.entries(cfg.profiles ?? {})) {
|
|
11614
|
+
checkServes(p.serves, ["profiles", name, "serves"]);
|
|
11615
|
+
}
|
|
11616
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
11617
|
+
checkServes(a.serves, ["agents", name, "serves"]);
|
|
11618
|
+
}
|
|
11583
11619
|
});
|
|
11584
11620
|
|
|
11585
11621
|
// src/config/paths.ts
|
|
@@ -11825,6 +11825,8 @@ var AgentMemorySchema = exports_external.object({
|
|
|
11825
11825
|
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."),
|
|
11826
11826
|
min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0\u20131.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default \u2014 " + "current behaviour). Try 0.10\u20130.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
|
|
11827
11827
|
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)."),
|
|
11828
|
+
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 an 8s timeout 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)."),
|
|
11829
|
+
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`."),
|
|
11828
11830
|
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."),
|
|
11829
11831
|
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).")
|
|
11830
11832
|
}).optional().describe("Auto-recall tuning knobs")
|
|
@@ -12038,8 +12040,12 @@ var ReleaseBlock = exports_external.object({
|
|
|
12038
12040
|
message: "release.channel and release.pin are mutually exclusive"
|
|
12039
12041
|
});
|
|
12040
12042
|
var NetworkIsolationSchema = exports_external.enum(["host", "strict"]).optional().describe("Container network mode (sec WS6-F1 #1390 / feature #1413). " + "'host' (DEFAULT when unset): `network_mode: host` \u2014 the agent " + "shares the host network stack; hindsight 127.0.0.1:18888 and " + "operator-LAN devices are reachable, but there is NO network " + "isolation from sibling agents or host services (the documented, " + "deliberate shared-host tradeoff). 'strict': the agent joins its " + "OWN dedicated docker bridge network instead \u2014 it cannot reach " + "sibling agents; host services are reached via " + "`host.docker.internal`. OPT-IN: validate hindsight / operator-" + "LAN / cron / boot-self-test paths for your deployment before " + "adopting fleet-wide (default-flip is deferred to that validation " + "cycle, #1413). Cascades override (agent \u2192 profile \u2192 defaults).");
|
|
12043
|
+
var servesField = exports_external.array(exports_external.string()).optional().describe("Users (keys in the top-level `users:` block) this agent works for. When " + "a served user messages this agent, their profile_bank is recalled " + "(speaker routing \u2192 memory.recall.sender_banks). Unions with any explicit " + "memory.recall.sender_banks. NOTE: this does not yet generate access " + "(allowFrom) \u2014 pair agent access as today; allowFrom generation is a " + "later phase.");
|
|
12044
|
+
var knowsField = exports_external.array(exports_external.string()).optional().describe("Users or banks this agent always knows as subjects \u2014 recalled and " + "recall-ranked even when that person is not the speaker (\u2192 " + "memory.recall.additional_banks). A `users:` key resolves to that user's " + "profile_bank; any other string is used as a raw bank name (e.g. a `kids` " + "profile bank with no Telegram identity). Unions with any explicit " + "memory.recall.additional_banks.");
|
|
12041
12045
|
var profileFields = {
|
|
12042
12046
|
extends: exports_external.string().optional(),
|
|
12047
|
+
serves: servesField,
|
|
12048
|
+
knows: knowsField,
|
|
12043
12049
|
bot_token: exports_external.string().optional(),
|
|
12044
12050
|
release: ReleaseBlock.optional().describe("Release-channel pin / pointer. Either `channel` (dev|rc|latest) or " + "`pin` (sha-<hex>|v<semver>) \u2014 mutually exclusive. Per-agent value " + "REPLACES the root entirely (no field merge)."),
|
|
12045
12051
|
timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC' " + "(three-letter aliases like EST/PST and bare offsets like UTC+10 are not accepted)").optional().describe("IANA timezone name (e.g. 'Australia/Melbourne', 'America/New_York', " + "'UTC'). Used to generate the per-turn local-time hint the agent's " + "UserPromptSubmit timezone hook emits, and baked into the agent " + "container's `environment.TZ` in compose so subprocess `date`/" + "`Date.now()` are correct. If unset at every cascade layer, switchroom " + "auto-detects from /etc/timezone and warns on `reconcile` when the " + "detected zone is UTC."),
|
|
@@ -12060,7 +12066,9 @@ var profileFields = {
|
|
|
12060
12066
|
recall: exports_external.object({
|
|
12061
12067
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
12062
12068
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
12063
|
-
min_overlap: exports_external.number().min(0).max(1).optional()
|
|
12069
|
+
min_overlap: exports_external.number().min(0).max(1).optional(),
|
|
12070
|
+
additional_banks: exports_external.array(exports_external.string()).optional(),
|
|
12071
|
+
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
12064
12072
|
}).optional()
|
|
12065
12073
|
}).optional(),
|
|
12066
12074
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
@@ -12105,6 +12113,8 @@ var { extends: _omitExtends, ...defaultsFields } = profileFields;
|
|
|
12105
12113
|
var AgentDefaultsSchema = exports_external.object(defaultsFields).optional();
|
|
12106
12114
|
var AgentSchema = exports_external.object({
|
|
12107
12115
|
extends: exports_external.string().optional().describe("Name of a profile to inherit from (e.g., 'coding', 'health-coach'). " + "Profiles may be defined inline under switchroom.yaml `profiles:` or as a " + "filesystem directory `profiles/<name>/`. Defaults to DEFAULT_PROFILE " + "('default') when unset."),
|
|
12116
|
+
serves: servesField,
|
|
12117
|
+
knows: knowsField,
|
|
12108
12118
|
bot_token: exports_external.string().optional().describe("Per-agent Telegram bot token or vault reference (overrides global telegram.bot_token)"),
|
|
12109
12119
|
release: ReleaseBlock.optional().describe("Per-agent release-channel pin / pointer. REPLACES the root " + "`release` block entirely (no field merge) \u2014 a pinned agent does " + "not inherit the fleet channel, and vice versa."),
|
|
12110
12120
|
bot_username: exports_external.string().optional().describe("Per-agent Telegram bot username (without leading @) when it doesn't " + "contain the agent slug. Replaces the default 'username includes slug' " + "preflight check with an exact (case-insensitive) match. Use when an " + "agent and its bot have intentionally divergent names (e.g. agent " + "'lawgpt' paired with bot '@meken_law_bot')."),
|
|
@@ -12278,6 +12288,11 @@ var CronEgressSchema = exports_external.object({
|
|
|
12278
12288
|
var CronConfigSchema = exports_external.object({
|
|
12279
12289
|
egress: CronEgressSchema.optional().describe("SSRF/exfil fence for http-diff polls.")
|
|
12280
12290
|
});
|
|
12291
|
+
var UserSchema = exports_external.object({
|
|
12292
|
+
name: exports_external.string().optional().describe("Display name for the user."),
|
|
12293
|
+
telegram_ids: exports_external.array(exports_external.string()).min(1).describe("Telegram username(s) and/or numeric user id(s) identifying this user " + "(a leading @ is optional). Matched against the message sender for " + "per-speaker memory routing."),
|
|
12294
|
+
profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`).")
|
|
12295
|
+
});
|
|
12281
12296
|
var SwitchroomConfigSchema = exports_external.object({
|
|
12282
12297
|
switchroom: exports_external.object({
|
|
12283
12298
|
version: exports_external.literal(1).describe("Config schema version"),
|
|
@@ -12324,10 +12339,31 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
12324
12339
|
})).optional().describe("RFC #1873: per-Microsoft-account ACL. Maps account email \u2192 list of " + "agents permitted to use that account's broker credentials. Written " + "by `switchroom auth microsoft enable|disable`; read by the broker " + "on get-credentials with provider=microsoft."),
|
|
12325
12340
|
defaults: AgentDefaultsSchema.describe("Implicit bottom-of-cascade profile applied to every agent before " + "per-agent config and `extends:` resolution. Tools, mcp_servers, and " + "schedule are unioned/concatenated; scalars and nested objects are " + "shallow-merged with per-agent values winning."),
|
|
12326
12341
|
profiles: exports_external.record(exports_external.string(), ProfileSchema).optional().describe("Named profile definitions. Agents reference via `extends: <name>`. " + "Inline profiles declared here take priority over filesystem " + "profiles/<name>/ directories when both exist."),
|
|
12342
|
+
users: exports_external.record(exports_external.string(), UserSchema).optional().describe("Trusted users the fleet serves \u2014 each a Telegram identity plus a " + "memory profile bank. Assigned to agents via `serves` / `knows`. The " + "operator's own trusted people (single-tenant), not multi-tenant. See " + "reference/rfcs/user-concept.md."),
|
|
12327
12343
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
12328
12344
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
12329
12345
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
12330
12346
|
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (\u00a76.1). Required to enable any http-diff poll; not agent-writable.")
|
|
12347
|
+
}).superRefine((cfg, ctx) => {
|
|
12348
|
+
const userKeys = new Set(Object.keys(cfg.users ?? {}));
|
|
12349
|
+
const checkServes = (serves, path) => {
|
|
12350
|
+
(serves ?? []).forEach((s, i) => {
|
|
12351
|
+
if (!userKeys.has(s)) {
|
|
12352
|
+
ctx.addIssue({
|
|
12353
|
+
code: exports_external.ZodIssueCode.custom,
|
|
12354
|
+
message: `serves references unknown user "${s}" \u2014 add it to the top-level ` + "`users:` block (or did you mean `knows` for a raw bank name?)",
|
|
12355
|
+
path: [...path, i]
|
|
12356
|
+
});
|
|
12357
|
+
}
|
|
12358
|
+
});
|
|
12359
|
+
};
|
|
12360
|
+
checkServes(cfg.defaults?.serves, ["defaults", "serves"]);
|
|
12361
|
+
for (const [name, p] of Object.entries(cfg.profiles ?? {})) {
|
|
12362
|
+
checkServes(p.serves, ["profiles", name, "serves"]);
|
|
12363
|
+
}
|
|
12364
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
12365
|
+
checkServes(a.serves, ["agents", name, "serves"]);
|
|
12366
|
+
}
|
|
12331
12367
|
});
|
|
12332
12368
|
|
|
12333
12369
|
// src/config/paths.ts
|