Skip to content

hermes update turns on the Connections toolset for installs with a saved toolset list - #108207

Closed
alt-glitch wants to merge 2 commits into
mainfrom
fix/connections-toolset-migration
Closed

alt-glitch wants to merge 2 commits into
mainfrom
fix/connections-toolset-migration

Conversation

@alt-glitch

@alt-glitch alt-glitch commented Sep 11, 2026 •

Copy link
Copy Markdown

After hermes update, every install that saved a toolset list before 2026-09-10 gets the manage_connections tool. 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 to config.yaml:

platform_toolsets:
  cli: [file, terminal, web]

The resolver reads "not in the list" as "the user unchecked it". The connections toolset, which carries manage_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]
Loading
Before (hermes chat -q, same prompt and saved list, before and after the migration) After (hermes chat -q, same prompt and saved list, before and after the migration)
Before After
The migration step (what hermes update runs post-pull), and config.yaml after
Migrate

What changes

One config migration, 42 → 43, in hermes_cli/config_migrations.py. hermes update runs 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:

Saved list looks like Migration does Why
[file, terminal, web], no record of the Connections checkbox appends connections shipped after the save
[file, terminal, web], known_builtin_toolsets records connections nothing user saw the checkbox on a newer build and left it off
[hermes-cli] nothing composite already expands to every core tool
[] nothing user chose no tools
platform where the toolset is not allowed nothing platform restriction

Words: known_builtin_toolsets is 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_toolsets is untouched. The resolver applies it last, so a Blank Slate install that lists connections there stays minimal.

Prints one line when it changes something:

✓ Enabled the Connections toolset (Gmail, Linear, Notion, ...) for cli, telegram — the manage_connections tool appears once you sign in to the Nous Portal. Uncheck Connections in `hermes tools` to turn it off.

What the user experiences

Before After hermes update
Saved list from before 09-10, signed in to a paid org agent: "I don't have that tool" manage_connections status returns the connector list
Same, then unchecks Connections in hermes tools n/a stays off; the save records the decline
Fresh install or [hermes-cli] composite present present, no change
Signed out, or free tier with no tool pool absent absent (check_fn, no change)
nix / apt / manual git pull, never runs hermes update absent absent until they run hermes config migrate or hermes tools once

Not 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_TOOLSETS frozenset 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 through scripts/run_tests.sh:

Test Base a3190625c0 This branch
stale list gains connections on every platform and in the record fail pass
declined / composite / empty list are untouched (3 cases) pass pass
agent.disabled_toolsets names it, list or JSON-string form: untouched, nothing claimed (2 cases) pass pass
second run adds nothing pass pass

Sibling files: test_config.py and test_tools_config.py, 173 passed, 10 skipped.

Live repro

Temp HERMES_HOME, real auth.json from a paid Nous org, config.yaml at v42 with platform_toolsets.cli: [file, terminal, web] and a known_builtin_toolsets.cli record without connections. Same hermes chat -q prompt before and after, fresh session each time. Ground truth from state.db, not the transcript.

State Command Tool the agent called (state.db) Reply
before hermes chat -q "..." none (0 tool calls) TOOL NOT AVAILABLE: manage_connections
migrate hermes config migrate (the step hermes update runs post-pull) n/a ✓ Enabled the Connections toolset ... for cli / Config version: 42 → 43; platform_toolsets.cli now lists connections
after identical hermes chat -q "..." manage_connections {"action": "status"} CONNECTED: googlecalendar, slack, linear, discord, figma, confluence, granola_mcp

Before the migration the agent made zero tool calls rather than hunting with tool_search: the search bridge only advertises manage_connections when 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 resulting config.yaml in one capture.

Review

Adversarial review by a zero-context subagent against the first draft found two defects, both fixed before this PR was opened:

  • Blank Slate installs and hermes tools --disable connections write agent.disabled_toolsets. The draft appended connections to 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 when connections is in agent.disabled_toolsets.
  • The draft's "is this an explicit list" test used built-in keys only. The resolver's own test also counts plugin keys, so a list like 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.

… 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.
@github-actions

github-actions Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on d58735e — chore: retrigger CI (zero-job dispatch failure, auto-heal)

⚠️ Warnings

OSV vulnerability scan · View job

80 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 6m3s vs 5m45s (+5.2%). 5 job(s) slower, 7 faster, 2 unchanged.

  • Python tests / Run tests: +36.0s
  • OS-specific tests / Windows-only tests: +33.0s
  • OSV scan / Emit review status: +30.0s
  • OS-specific tests / macOS-only tests: -13.0s
  • Python tests / e2e: -6.0s

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard comp/tools Tool registry, model_tools, toolsets area/config Config system, migrations, profiles area/install-update Installer, updater, packaging, wheels, doctor sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 11, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. Blank Slate from before the toolset existed is treated as a stale picker. hermes_cli/config_migrations.py:561 only bails if connections is already in agent.disabled_toolsets. Blank Slate writes cli: [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) — appends connections, 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 / large disabled_toolsets even when connections is absent.

  2. 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_toolsets had so hermes-webhook stayed narrow. “Configure all platforms” copies [file, terminal, web] onto webhook → this step adds connections → untrusted webhook sessions get manage_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 when resolve_toolset("connections") ⊆ resolve_toolset(default_toolset(platform)).

  3. agent.disabled_toolsets: [all] / "all" / "*" still writes and claims an enable. The string connections is not in that list, so line 561 does not return. The resolver then subtracts everything. Same false-enable already fixed for a named connections disable. Treat all/* like a connections disable (or skip the claim).

Warnings

  1. A one-item plugin allowlist (cli: [spotify]) gains connections. 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, no config_added
  • platform_toolsets.webhook: [file, terminal, web] → webhook unchanged
  • agent.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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants