hermes update turns on the Connections toolset for installs with a saved toolset list - #108207
alt-glitch wants to merge 2 commits into
Conversation
… toolset lists `hermes tools` writes an explicit `platform_toolsets.<platform>` list, and the resolver reads absence from that list as "unchecked". The `connections` toolset (#106842) shipped after most users last saved, so `manage_connections` is stripped from the schema on every install that ever opened the picker. The Nous entitlement gate never runs; the agent reports the tool as missing. Migration 42 -> 43 appends `connections` to each explicit per-platform list that lacks it and records the offer in `known_builtin_toolsets` where that record exists, so a later uncheck reads as a decline. It skips: platforms whose record already holds `connections` (the user saw the checkbox and left it off), bare composite lists ([hermes-cli]) that already inherit it, platforms where the toolset is not allowed, and any config whose `agent.disabled_toolsets` names `connections` (Blank Slate, `hermes tools --disable`), because the resolver subtracts that list last and the enable would never take effect. The explicit-list test is the resolver's own: any configurable or plugin key. `hermes update` runs migrations post-pull for the active profile and every sibling, so one update is enough. Fresh installs and composite users were never affected.
૮ >ﻌ< ა ci reviewran on d58735e — chore: retrigger CI (zero-job dispatch failure, auto-heal)
|
teknium1
left a comment
There was a problem hiding this comment.
PR Review — #108207
Verdict: Request changes
Head: d58735e
Premise: confirmed against origin/main. _RECENTLY_SHIPPED_TOOLSETS is empty, so a pre-2026-09-10 explicit platform_toolsets list still strips connections. Composite [hermes-cli] users already inherit it. The reporter path is real.
Tests: 7 passed on the PR tree (tests/hermes_cli/test_config_migration_43_connections.py). Those cases never instantiate webhook, Blank Slate-before-shipping, or disabled_toolsets: [all].
Live test: migration + _get_platform_tools over ~30 config shapes on this head (temp HERMES_HOME, real imports). Reporter CLI picker path: before off, after on. The holes below all fired.
Critical
-
Blank Slate from before the toolset existed is treated as a stale picker.
hermes_cli/config_migrations.py:561only bails ifconnectionsis already inagent.disabled_toolsets. Blank Slate writescli: [file, skills, terminal, vision]plus a long disable list that cannot name a toolset that did not exist yet (setup_quick.py::_blank_slate_minimal_toolsets). On this head:- modern Blank Slate (disable list already includes
connections) — skipped, good - pre-ship Blank Slate (same keep-4, disable list without
connections) — appendsconnections, resolver turns it on - keep-4 only (disabled compute skipped) — same enable
Empty
[]is correctly left alone. This is the same class of “user chose a small set” and should stay off. Skip keep-4 / largedisabled_toolsetseven whenconnectionsis absent. - modern Blank Slate (disable list already includes
-
Webhook (and other narrow composites) is widened.
toolset_allowed_for_platform("connections", *)is unrestricted (toolset_scope.py). There is no “subset of this platform’s default composite” check, which_enable_recently_shipped_toolsetshad sohermes-webhookstayed narrow. “Configure all platforms” copies[file, terminal, web]onto webhook → this step addsconnections→ untrusted webhook sessions getmanage_connections. ACP explicit lists also gain it; ACP’s default composite already had the tool, webhook’s did not. Reuse the composite-subset rule: only append whenresolve_toolset("connections")⊆resolve_toolset(default_toolset(platform)). -
agent.disabled_toolsets: [all]/"all"/"*"still writes and claims an enable. The stringconnectionsis not in that list, so line 561 does not return. The resolver then subtracts everything. Same false-enable already fixed for a namedconnectionsdisable. Treatall/*like a connections disable (or skip the claim).
Warnings
- A one-item plugin allowlist (
cli: [spotify]) gainsconnections. Empty[]is skipped; a deliberate one-tool list is not. Mixed[hermes-cli, spotify]already inherited it from the composite, so that write is redundant. If the intent is “picker users who had a real builtin subset”, require at least one builtin configurable key, not any plugin key.
Covered and fine
Composite [hermes-cli], empty [], declined via known_builtin_toolsets, named connections disable (list and ['connections'] string), no/null platform_toolsets, YAML string (not a list), missing/null known, split platforms, cron/api_server/discord explicit, v41 ladder, idempotent second run, sibling-profile update path.
Tests to add (the current 7 do not catch 1–3)
- keep-4 Blank Slate with a disable list that does not name
connections→ list unchanged, noconfig_added platform_toolsets.webhook: [file, terminal, web]→ webhook unchangedagent.disabled_toolsets: [all]→ list unchanged, no claimed enable
Do not merge on the current suite. The reporter CLI path is right; these three configs are not.
After
hermes update, every install that saved a toolset list before 2026-09-10 gets themanage_connectionstool. Until now those installs never saw it, even signed in to a paid Nous org.The problem
hermes tools(and the desktop Toolsets panel) writes an explicit list of toolset names toconfig.yaml:The resolver reads "not in the list" as "the user unchecked it". The
connectionstoolset, which carriesmanage_connections, shipped on 2026-09-10 (#106842). It is absent from every list saved before that date. So the toolset is off, the tool is stripped from the model's schema, and the agent reports it as missing. The Nous sign-in check never gets a vote.Users who never opened the picker are on the
[hermes-cli]composite, which expands to every core tool at read time. They got the tool on update. Picker users did not. Same release, two outcomes.flowchart LR U[hermes update] --> C[new code on disk] C --> R{platform_toolsets.cli<br/>saved before 09-10?} R -- "[hermes-cli] composite" --> ON[connections on] R -- "[file, terminal, web]" --> OFF[connections off<br/>manage_connections absent]hermes updateruns post-pull), andconfig.yamlafterWhat changes
One config migration,
42 → 43, inhermes_cli/config_migrations.py.hermes updateruns migrations after the pull for the active profile and every sibling profile (update_cmd_config.py:46,84), so one update is enough. Docker runs the same step on container boot.For each platform in
platform_toolsets:[file, terminal, web], no record of the Connections checkboxconnections[file, terminal, web],known_builtin_toolsetsrecordsconnections[hermes-cli][]Words:
known_builtin_toolsetsis the list of checkboxes the picker showed at last save, written on every save since July. A toolset in that record but not in the saved list is a decline. A toolset in neither is one that did not exist yet.agent.disabled_toolsetsis untouched. The resolver applies it last, so a Blank Slate install that listsconnectionsthere stays minimal.Prints one line when it changes something:
What the user experiences
hermes updatemanage_connections statusreturns the connector listhermes tools[hermes-cli]compositecheck_fn, no change)git pull, never runshermes updatehermes config migrateorhermes toolsonceNot in this PR
The general rule ("a new built-in toolset is on for everyone who has not declined it, with no per-toolset code") is a resolver change. Built-ins would follow the rule plugin toolsets already get from
known_plugin_toolsets(tools_config.py:536-542). That deletes the_RECENTLY_SHIPPED_TOOLSETSfrozenset and its six skip-when-empty tests. Separate PR; this one unblocks users today.#108142 (the frozenset approach) is closed in favour of this.
Tests
tests/hermes_cli/test_config_migration_43_connections.py, 4 functions, 7 cases, run throughscripts/run_tests.sh:a3190625c0connectionson every platform and in the recordagent.disabled_toolsetsnames it, list or JSON-string form: untouched, nothing claimed (2 cases)Sibling files:
test_config.pyandtest_tools_config.py, 173 passed, 10 skipped.Live repro
Temp
HERMES_HOME, realauth.jsonfrom a paid Nous org,config.yamlat v42 withplatform_toolsets.cli: [file, terminal, web]and aknown_builtin_toolsets.clirecord withoutconnections. Samehermes chat -qprompt before and after, fresh session each time. Ground truth fromstate.db, not the transcript.state.db)hermes chat -q "..."TOOL NOT AVAILABLE: manage_connectionshermes config migrate(the stephermes updateruns post-pull)✓ Enabled the Connections toolset ... for cli/Config version: 42 → 43;platform_toolsets.clinow listsconnectionshermes chat -q "..."manage_connections {"action": "status"}CONNECTED: googlecalendar, slack, linear, discord, figma, confluence, granola_mcpBefore the migration the agent made zero tool calls rather than hunting with
tool_search: the search bridge only advertisesmanage_connectionswhen the Connections toolset is granted (tools/tool_search.py:217-239), so the tool was invisible to search too. Screenshots in the block above; the middle frame shows the migration output and the resultingconfig.yamlin one capture.Review
Adversarial review by a zero-context subagent against the first draft found two defects, both fixed before this PR was opened:
hermes tools --disable connectionswriteagent.disabled_toolsets. The draft appendedconnectionsto the platform list and printed✓ Enabled, but the resolver subtracts that list last, so the enable never took effect and the message was false. Now the step returns early whenconnectionsis inagent.disabled_toolsets.cli: [spotify]was misread as a composite and skipped. Now uses the same predicate the resolver does.Confirmed OK: int keys, null
known_builtin_toolsets, unknown platform names, mixed[file, hermes-cli]lists, docker boot, sibling profiles, idempotency.