Connector tools (Gmail, Linear, Notion, ...) are searchable and callable through tool_search for signed-in Nous users - #106842
Merged
Conversation
…lement — no free-tier leg The port carried a third availability leg from hermes-magic: a stored guest (free-tier) identity short-circuits the managed-tool entitlement check. That leg reads hermes_cli.anon_auth, which does not exist on hermes-agent main, so connectors_available() raised ImportError inside its fail-closed try and the whole connector surface was silently dark on a plain upstream checkout. On this tree availability is the two-leg AND the design started with: tools.connectors.enabled AND managed_nous_tools_enabled(). The free-tier leg is a hermes-magic concern and belongs in hermes-magic's own delta over this branch, next to the identity it depends on. Its integration test goes with it.
The squashed port carried the code but not the user-facing docs. Restores the Connectors section of the Tool Search page and the connector-gateway host / CONNECTOR_GATEWAY_URL override on the Tool Gateway page, updated for the manage_connections tool and the pure-connector batch rule.
…nstead of taking leftover slots dispatch_tool_search ran BM25 over the local catalog, filled `limit` slots, then appended connector hits only into slots left empty. On a 300-tool catalog no slot was ever empty, so with Gmail and Google Calendar connected "send gmail email" returned five betterstack tools and zero connector tools. The gateway's hits for a query now become catalog entries (connector name, slug words, description as the search text) and join the local catalog for that query's BM25 pass. One ranking, one rarest-token admission rule for both sources, `limit` as the total per query. The merge loop and the separate record builder for connector hits are gone; `_shared_tool_record` serves both sources. The gateway search timeout rises from 8 s to 30 s. One request with six use_cases measured 7 s, so 8 s sat on the edge and cut real answers off; the failure path is unchanged (local-only results, no error to the model). Live, 311 local tools + gateway, before -> after: "send gmail email": 5 betterstack tools -> gmail SEND_EMAIL, CREATE_EMAIL_DRAFT "read google calendar events": 5 betterstack tools -> googlecalendar EVENTS_LIST_ALL_CALENDARS "linear create issue", "betterstack incident": unchanged Benchmark (25 labelled queries): connector recall 0.09 -> 0.82, precision@5 0.18 -> 0.59, false positives on absent intents 17 -> 2.
tools/tool_search.py is a facade. The connector leg (gateway hits as catalog entries for tool_search, remote schemas for tool_describe, the connections_in_scope gate) was appended to it by the port. It now lives in its own sibling, tools/connector_search.py, and the facade imports the three entry points: connections_in_scope, connector_entries_by_group, remote_schemas_for. No behaviour change. The tool_describe remote block became remote_schemas_for(names, current_tool_defs, connector_describe) with the same inputs, the same silent-degradation contract and the same injection seam the tests already use.
One tool_search call sends all its queries to the connector gateway as one search request. The gateway answers 7 use_cases per request and returns HTTP 502 for 8 or more (measured 2026-09-09, re-measured with one-word use_cases: it is a count limit, not a size limit). With the client cap at 10, a model sending 8 to 10 queries lost every connector hit for that call and saw local-only results with no error. The shared constant splits: _MAX_QUERIES_PER_CALL = 7 for search, _MAX_DESCRIBE_NAMES_PER_CALL = 10 for describe, which has no remote count limit. Eight or more queries now get the existing "too many queries" retry hint before any request is made. No chunking: one call, one request.
…e_connections accounts tool_search results carry names like connectors__gmail__CREATE_EMAIL_DRAFT and manage_connections is the tool that checks and connects those accounts, but nothing told the model the two are the same thing. A model that hit CONNECTION_REQUIRED had to infer the fix on its own. The tool_search description gains one sentence making the link, added at assembly only when manage_connections is in the session's tools. Signed out or with connectors off the tool is absent and the description is unchanged, so it never names a tool the model cannot call. This follows the existing rule for cross-tool references (tools/AGENTS.md): they are added dynamically from the session's actual tool set, never hardcoded in a schema. Tool defs are fixed for the life of a conversation, so the description is byte-stable per conversation; this is a one-time prefix change. Live, real get_tool_definitions() against a signed-in home: sentence present. Same home with auth.json removed: manage_connections absent, sentence absent.
… call dispatch_connector_batch runs every remote entry of a tool_call batch in sequence. The executor only checks the interrupt flag between tools, and the whole batch is one tool to it, so a /stop landing during entry 1 of 20 still sent the other 19 to the gateway. The loop now reads tools.interrupt.is_interrupted before each dispatch. Once set, it stops calling handle_function_call and fills every unstarted slot with the loop's existing error-slot shape, code INTERRUPTED and the message "Stopped by the user before this call was made.", so the result envelope stays valid and the counts stay honest. Entries already dispatched keep their real results. Test: three connector calls where the fake client sets the interrupt on the first execute. The client sees exactly one call and slots 2 and 3 carry INTERRUPTED. Red on the base branch, green with the fix.
test_schema_documents_wait_and_its_timeout froze description fragments
("REQUIRED", "can NOT disconnect", "Nous Portal"). A wording edit fails
it while a real regression (a disconnect that reaches the gateway) does
not. That is a snapshot of prose, not a behaviour contract.
Delete it. The requirement that wait needs connectors is already covered
by test_wait_requires_connectors. The user-only disconnect boundary is
now asserted as behaviour: action disconnect with a connector returns an
error and the fake client records no call. That replaces the earlier
de-authenticate test, which only checked that the word "dashboard"
appeared in the error text.
Test count in the file goes from 26 to 25.
The user guide said a connector batch travels as one gateway request. It
does not: model_tools_connectors.dispatch_connector_batch re-enters core
dispatch per entry, and each entry becomes its own execute request in
bridge._run_remote (plus at most one literal-slug retry when the gateway
reports TOOL_NOT_FOUND under the conventional slug). The docstrings in
tools/tool_gateway/bridge.py and tools/tool_gateway/__init__.py still
described the abandoned V1 plan and claimed nothing outside the package
imports it.
Rewrite those sentences to match the code: one request per entry, in
input order, dispatched from model_tools_connectors.py, with the per-entry
approval and interrupt behaviour that motivated the split. The guide also
still showed the single-call shape tool_call(name, arguments); both
places now show the `calls: [{name, arguments}]` array the schema
advertises and note that a single local call is an array of one.
Docs only, no test.
The per-turn MCP refresh folds a fresh tool snapshot into the live array with preserve_prefix: order and membership stay, but a name present in both takes the fresh schema. That is right for ordinary tools, whose schema is a constant. tool_search is the one tool whose description is derived from the session: the deferred-tool count, the embedded listing, and, on this branch, whether manage_connections was present. A late MCP server or one failed portal lookup (manage_connections' check_fn fails closed) changed those bytes on the next turn, and every byte after tool_search in the cached prefix was re-prefilled. The array also contradicted itself in that case: the flapping manage_connections was carried forward while the description lost its hint. The bridge entries now keep the bytes they were built with for the life of the conversation. Nothing is lost: tool_search reads the live catalog at dispatch, so tools that arrived late are still found; connector availability is checked at dispatch too. The compaction-boundary rebuild (content_aware, the one sanctioned cache break) still refreshes the description. Consequence: connector exposure in the prompt is decided once, at agent build, by whether the user was signed in then. That is the intended contract.
…er argument validation The port appended the tool_call argument parser to the tool_search facade. The family already has tools/tool_search_validation.py for exactly this work (schema validation of deferred call arguments), so the parser moves there and the facade imports it. No behaviour change; the one test that imported it now imports from the defining module.
… becomes run_remote
bridge.dispatch_calls and its helpers (_dispatch_calls_inner, _run_pre_dispatch,
_run_local, _error_slot, _maybe_parse_json) and the LocalDispatch / PreDispatch
seams had no production caller. Connector dispatch runs through
model_tools_connectors: dispatch_connector_batch re-enters handle_function_call
once per entry, so scope, hook, approval and middleware policy fire against each
composed name inside core dispatch, and dispatch_connector_call hands the single
planned entry to the bridge's transport function. Only tests called the batch
dispatcher, and they exercised policy seams that production never wires.
The transport function is the module's real entry point, so it drops the
underscore: _run_remote becomes run_remote, body unchanged. The module
docstring now describes the two legs that exist (availability with D32 silent
degradation, and run_remote) instead of the injected seams. Imports that only
the deleted code used are gone; merge.py is untouched because every export
still has a caller.
Tests that drove dispatch_calls are deleted where they covered the removed
seams (pre_dispatch blocks and rewrites, local_dispatch classification, mixed
batches). The literal-slug fallback, the per-entry transport failure, and the
hook rewrite reaching the gateway request body are re-targeted at
handle_function_call('tool_call', ...) with the fake client swapped in at
bridge._default_client_factory, the same seam test_connector_dispatch_policy
uses. Each re-targeted test fails when the retry is disabled in run_remote.
Contributor
૮ >ﻌ< ა ci reviewran on 5e6107d — fix(connectors): search keeps the twin a colliding name reac
|
alt-glitch
added this pull request to stack #106845
September 9, 2026 20:00
…says so format_connector_name strips the toolkit prefix, so GMAIL_FETCH_PROFILE and a literal FETCH_PROFILE on gmail both compose to connectors__gmail__FETCH_PROFILE. describe and execute decode that name to the prefixed slug first, so the literal twin is unreachable under it. If a vendor ever shipped both, search could describe the literal under a name that runs the prefixed tool. Search is the one place that sees both twins in one response. It now keeps the twin the name reaches and drops the other with a WARNING that names both slugs, whichever the gateway listed first. Short names stay; no marker, no per-process map, no change to describe or execute. No such pair exists in the live catalog today; the guard turns a silent alias into a logged one.
2 tasks
This was referenced Sep 11, 2026
Closed
gabrielcosi
pushed a commit
to gabrielcosi/home-ops
that referenced
this pull request
Sep 12, 2026
…9.7 ➔ v2026.9.11) (#754) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/gabrielcosi/hermes-agent](https://github.com/NousResearch/hermes-agent) | patch | `v2026.9.7` → `v2026.9.11` | --- ### Release Notes <details> <summary>NousResearch/hermes-agent (ghcr.io/gabrielcosi/hermes-agent)</summary> ### [`v2026.9.11`](https://github.com/NousResearch/hermes-agent/releases/tag/v2026.9.11): Hermes Agent v0.21.2 (v2026.9.11) [Compare Source](NousResearch/hermes-agent@v2026.9.7...v2026.9.11) ##### Hermes Agent v0.21.2 (v2026.9.11) — The state.db Patch Release **Release Date:** September 11, 2026 > Patch release. v0.21.0 shipped a large rewrite of the session store's connection handling, and for some installs it made `state.db` fragile: second writers cancelling each other's locks, healthy databases reported as corrupt, one bad row killing `sessions list`. This release closes that class and rolls up everything else that landed on `main` in the four days since v0.21.1. ##### About this release Measured at commit `04dd80a977f40b05e5b2054111747af07a61886a`, the window since v0.21.1 contains **947 non-merge commits** across **1,869 changed files** (+182,504 / −15,564) and **312 merged PRs**. **140 contributors** appear in commits, co-author trailers, or salvage credits. ##### ✨ Highlights ##### state.db reliability campaign (six PRs, 44 issues closed) If your `state.db` broke after 0.21.0, this is the release for you. Six PRs fix the root causes rather than the symptoms: - **No more second writers.** Profile gateways wrote hosted-room state into the *root* `state.db` every 5 seconds; the dashboard opened a writable handle on startup; cron's lifecycle guard did a raw `open()` on a live database (which cancels the gateway's POSIX locks — the classic "how to corrupt SQLite" recipe); `doctor --fix` would checkpoint under a live holder. All four are gone: hosted rooms live in `shared-state.db`, the dashboard opens read-only first, the guard goes through the tracked connection registry, and `doctor --fix` refuses a checkpoint it can't prove is safe. ([#​108076](NousResearch/hermes-agent#108076) — salvage [#​103489](NousResearch/hermes-agent#103489) [@​RikETS](https://github.com/RikETS), [#​102682](NousResearch/hermes-agent#102682) [@​JoaoMarcos44](https://github.com/JoaoMarcos44), [#​108012](NousResearch/hermes-agent#108012) [@​Halldrix](https://github.com/Halldrix), [#​105428](NousResearch/hermes-agent#105428) [@​TaoMasterCoder](https://github.com/TaoMasterCoder)) - **Healthy WAL databases stop wedging.** OpenZFS `(deleted)` dentries and a `close()` racing an `append_message` both produced a sticky `DeletedWalGenerationError` on a perfectly good store; the read pool was handed out under an unconfirmed journal mode; a transient `disk I/O error` on WSL2 killed `get_session` on the first attempt; and a "state.db locked" banner was broadcast after the lock had already cleared. ([#​108082](NousResearch/hermes-agent#108082) — salvage [#​107411](NousResearch/hermes-agent#107411) [@​chelsealong](https://github.com/chelsealong), [#​105578](NousResearch/hermes-agent#105578) [@​ca-shrimp](https://github.com/ca-shrimp), [#​105711](NousResearch/hermes-agent#105711) [@​gaoanze888](https://github.com/gaoanze888), [#​106958](NousResearch/hermes-agent#106958) [@​nikkoxgonzales](https://github.com/nikkoxgonzales); co-authored [@​QDung210](https://github.com/QDung210), [@​fangliquanflq](https://github.com/fangliquanflq), [@​Sahilvishnaliya](https://github.com/Sahilvishnaliya)) - **FTS damage no longer kills your turn.** An error scoped to the full-text-search index was classified as whole-file corruption and fail-closed the conversation. It's now `fts_index`: search degrades, the index rebuilds later, the transcript store is untouched. Same PR: doctor names structural damage honestly instead of "FTS write corruption", the FTS write probe catches the stale-index shape that passed every check while every write failed, `.recover` output no longer fails startup on orphan FTS5 shadow tables, header-zeroed databases recover instead of being refused, and the dashboard analytics poller returns a 503 instead of 520K tracebacks a day. ([#​108130](NousResearch/hermes-agent#108130) — salvage [#​97843](NousResearch/hermes-agent#97843) [@​SulthanZahran1](https://github.com/SulthanZahran1) + [#​97841](NousResearch/hermes-agent#97841) [@​Finn763](https://github.com/Finn763), [#​88604](NousResearch/hermes-agent#88604) [#​56824](NousResearch/hermes-agent#56824) [#​103657](NousResearch/hermes-agent#103657) [@​liuhao1024](https://github.com/liuhao1024), [#​106890](NousResearch/hermes-agent#106890) [@​nftpoetrist](https://github.com/nftpoetrist), [#​103321](NousResearch/hermes-agent#103321) [@​jangomango76](https://github.com/jangomango76), [#​91413](NousResearch/hermes-agent#91413) [@​leegunwoo98](https://github.com/leegunwoo98), [#​102808](NousResearch/hermes-agent#102808) [@​TaoMasterCoder](https://github.com/TaoMasterCoder)) - **One corrupt row no longer kills `sessions list`, export, or insights.** A TEXT timestamp or a `1e30` epoch used to crash the whole listing; malformed marker JSON crashed `json_extract`; more than 999 ids crashed bulk delete/prune. One `coerce_epoch()` helper on every reader (bad rows render `?` with a WARNING naming the session), a `json_valid` guard, IN-list chunking, and batched export hydration. ([#​108086](NousResearch/hermes-agent#108086) — salvage [#​106071](NousResearch/hermes-agent#106071) [@​Xipong](https://github.com/Xipong), [#​101726](NousResearch/hermes-agent#101726) [@​efe-arv](https://github.com/efe-arv), [#​94701](NousResearch/hermes-agent#94701) [@​liuhao1024](https://github.com/liuhao1024), [#​102679](NousResearch/hermes-agent#102679) [@​mssteuer](https://github.com/mssteuer), [#​100658](NousResearch/hermes-agent#100658) [@​Mi55ed](https://github.com/Mi55ed)) - **Sessions never bind to or read another profile's database.** The Desktop launch backend could pin itself to the wrong profile's `state.db` under a HERMES\_HOME override race; `session_search` by bare ID silently scanned every profile and returned someone else's transcript; recovery guidance pointed at the wrong file; profile delete kept a handle open (WinError 32). ([#​108074](NousResearch/hermes-agent#108074) — salvage [#​102534](NousResearch/hermes-agent#102534) [@​HexLab98](https://github.com/HexLab98), [#​106975](NousResearch/hermes-agent#106975) [@​Sora-bluesky](https://github.com/Sora-bluesky)) - **Opening state.db no longer takes the write lock when nothing needs writing.** A one-shot `hermes` process opening the store behind a busy gateway stalled 4–20 s and then failed with "database is locked". Now 0.01 s. ([#​108067](NousResearch/hermes-agent#108067) — salvage [#​106751](NousResearch/hermes-agent#106751) [@​kshitijk4poor](https://github.com/kshitijk4poor), [#​101881](NousResearch/hermes-agent#101881) [@​jonpol01](https://github.com/jonpol01)) Also in the window from the same subsystem: a fresh `state.db` no longer publishes FTS tables before owning the rebuild lock ([#​106311](NousResearch/hermes-agent#106311)), a handle that lost its WAL generation no longer checkpoints stale frames at shutdown ([#​106315](NousResearch/hermes-agent#106315), [#​106840](NousResearch/hermes-agent#106840)), a clobbered first page is quarantined with its WAL instead of opened destructively ([#​106587](NousResearch/hermes-agent#106587)), WAL setup leaves an unverifiable database untouched ([#​106568](NousResearch/hermes-agent#106568)), and quarantined handles refuse VACUUM/FTS optimize ([#​106343](NousResearch/hermes-agent#106343), [#​106349](NousResearch/hermes-agent#106349)). Most of these salvaged community diagnoses by [@​kshitijk4poor](https://github.com/kshitijk4poor). ##### Multi-profile isolation hardening A cluster of fixes for installs running several profiles under one gateway (multiplex): secondary-profile bots no longer inherit the default profile's allow-lists ([#​107616](NousResearch/hermes-agent#107616)), adapters no longer send credentials to the default profile's host ([#​107617](NousResearch/hermes-agent#107617)), stdio MCP servers no longer receive the default profile's vault secrets ([#​107630](NousResearch/hermes-agent#107630)), `MEDIA:` delivery can no longer attach another profile's `.env` / `auth.json` / `state.db` ([#​107609](NousResearch/hermes-agent#107609)), Feishu drive callbacks and `/p/<profile>/` webhook replies stay on the routed profile ([#​107620](NousResearch/hermes-agent#107620), [#​107626](NousResearch/hermes-agent#107626)), and secondary profiles no longer get a sibling's Nous bearer from per-process memos ([#​107611](NousResearch/hermes-agent#107611)). ##### Desktop backend spawn storms are over Bot Mode used to spawn or dial one backend per profile on launch and on every roster tick, hovering the Bots roster spawned a backend per row, and profile switches could spawn a duplicate primary. ([#​108069](NousResearch/hermes-agent#108069), [#​108107](NousResearch/hermes-agent#108107), [#​108118](NousResearch/hermes-agent#108118), [#​108134](NousResearch/hermes-agent#108134), [#​107969](NousResearch/hermes-agent#107969), [#​108112](NousResearch/hermes-agent#108112) — salvage [#​102512](NousResearch/hermes-agent#102512), [#​103634](NousResearch/hermes-agent#103634), [#​103399](NousResearch/hermes-agent#103399), [#​107997](NousResearch/hermes-agent#107997) and others by [@​kshitijk4poor](https://github.com/kshitijk4poor)) ##### Password-blind credential vault The agent can now sign in, pay, and fill addresses from 1Password, Bitwarden, or the local Hermes vault without ever seeing a secret; two-factor codes come from a saved authenticator key or are asked for in the user's UI ([#​106480](NousResearch/hermes-agent#106480), [#​107585](NousResearch/hermes-agent#107585)). Private git plugins install with the user's stored credentials ([#​106981](NousResearch/hermes-agent#106981)). ##### Plugin catalog and one Plugins page A curated, SHA-pinned plugin index with CLI, admission CI, docs and dashboard ([#​69446](NousResearch/hermes-agent#69446)); Desktop gets one Plugins page owning agent + desktop plugins, install, catalog and per-commit pinning ([#​107212](NousResearch/hermes-agent#107212), [#​107314](NousResearch/hermes-agent#107314), [#​107321](NousResearch/hermes-agent#107321)); Radio ships as an opt-in SDK plugin ([#​107072](NousResearch/hermes-agent#107072)). ##### Nous free tier and guided first launch Free inference and connectors out of the box with one command to sign in ([#​105258](NousResearch/hermes-agent#105258), [#​105260](NousResearch/hermes-agent#105260)), `/login` from a chat ([#​105261](NousResearch/hermes-agent#105261)), connector tools (Gmail, Linear, Notion, ...) searchable through `tool_search` ([#​106842](NousResearch/hermes-agent#106842)), and a guided first launch behind `HERMES_GUEST_ONBOARDING=1` ([#​107697](NousResearch/hermes-agent#107697), [#​107958](NousResearch/hermes-agent#107958), [#​107985](NousResearch/hermes-agent#107985), [#​108211](NousResearch/hermes-agent#108211)). ##### 🐛 Notable Bug Fixes **Gateway & platforms** - A bare `display:` key in config.yaml no longer crashes every gateway turn ([#​106305](NousResearch/hermes-agent#106305)); a queued-lane final refused by the platform is recorded and redelivered ([#​106316](NousResearch/hermes-agent#106316)); a stalled WebSocket send no longer blocks every later event ([#​106581](NousResearch/hermes-agent#106581)); the first turn no longer waits on the Python toolchain probe ([#​106556](NousResearch/hermes-agent#106556)). - Telegram bots must @​mention when `bots_require_mention` is on, breaking bot-to-bot loops ([#​106534](NousResearch/hermes-agent#106534)); Matrix renders LaTeX ([#​106515](NousResearch/hermes-agent#106515)); Signal renders markdown tables ([#​106538](NousResearch/hermes-agent#106538)); WhatsApp replies to view-once messages keep their quote ([#​106541](NousResearch/hermes-agent#106541)); media-only replies report SUCCESS everywhere ([#​106557](NousResearch/hermes-agent#106557)). **Providers & routing** - `/model` and auxiliary auto never bill a provider you didn't select ([#​107366](NousResearch/hermes-agent#107366)); never auto-switch to a provider you have no credentials for ([#​107281](NousResearch/hermes-agent#107281)); Bedrock Claude/Converse/Mantle models survive `/model`, fallback and restore ([#​107621](NousResearch/hermes-agent#107621), [#​107658](NousResearch/hermes-agent#107658)); Bedrock Guardrails enforced ([#​107815](NousResearch/hermes-agent#107815)). - Codex: patch-budget image 400 shrinks and retries ([#​106525](NousResearch/hermes-agent#106525)); unentitled primary + fallback no longer oscillate ([#​106549](NousResearch/hermes-agent#106549)); Azure Foundry replayed-reasoning rejection classified and pruned ([#​106718](NousResearch/hermes-agent#106718) [@​erosika](https://github.com/erosika)). MCP OAuth refresh no longer erases the refresh token ([#​106185](NousResearch/hermes-agent#106185)). Anthropic clients send exactly one credential ([#​107978](NousResearch/hermes-agent#107978)). - DeepSeek V4.1 Flash on Nous Portal and OpenRouter pickers ([#​107489](NousResearch/hermes-agent#107489)); GPT Image 2.5 via OpenAI and FAL ([#​105988](NousResearch/hermes-agent#105988)); Opus 5 / Fable 5.1 on the native Anthropic picker ([#​106636](NousResearch/hermes-agent#106636) [@​xxxigm](https://github.com/xxxigm)). **Agent loop & compression** - One blocked periodic callback no longer stalls lease refresh ([#​106308](NousResearch/hermes-agent#106308)); a mid-turn `/steer` is persisted as its own user row ([#​106317](NousResearch/hermes-agent#106317), [#​106344](NousResearch/hermes-agent#106344)); local-inference memory-ceiling rejections back off instead of compressing history ([#​106307](NousResearch/hermes-agent#106307)); context-overflow after partial streaming ends the turn ([#​106567](NousResearch/hermes-agent#106567)); length continuation stops when the prompt filled the window ([#​106571](NousResearch/hermes-agent#106571)); compression no longer times out silently on aux retries ([#​106866](NousResearch/hermes-agent#106866)); `model_thresholds` keys can be provider-scoped ([#​108061](NousResearch/hermes-agent#108061)). - Surface switch (Desktop↔TUI) no longer rebuilds the system prompt and busts the prompt cache ([#​105844](NousResearch/hermes-agent#105844)); CLI keeps the `api_content` sidecar so the cache survives an early persist ([#​105842](NousResearch/hermes-agent#105842)). **CLI, TUI & Desktop** - `hermes -z --resume` continues the session ([#​106313](NousResearch/hermes-agent#106313)); Shift+letter and Cmd+Shift+Z work on extended-key terminals ([#​90674](NousResearch/hermes-agent#90674) [@​francip](https://github.com/francip), [#​105493](NousResearch/hermes-agent#105493)); `browser_exec` timeout kills the whole process tree ([#​106589](NousResearch/hermes-agent#106589)); update checks poll the GitHub API once a day instead of git-fetching every 30 min ([#​107648](NousResearch/hermes-agent#107648)); `hermes update` names the real cause and can't hang on a stalled fetch ([#​108053](NousResearch/hermes-agent#108053)). - Desktop: UI language survives the update relaunch ([#​106476](NousResearch/hermes-agent#106476)), expired OAuth grants get a one-click re-sign-in ([#​106965](NousResearch/hermes-agent#106965)), HUD mode shows the transcript again and always gives the window back ([#​107491](NousResearch/hermes-agent#107491), [#​107423](NousResearch/hermes-agent#107423)), the backend exits when its Desktop parent dies ([#​107977](NousResearch/hermes-agent#107977)), Windows updates stop reporting false failures ([#​106175](NousResearch/hermes-agent#106175), [#​107183](NousResearch/hermes-agent#107183)), WSLg renders on the Windows GPU ([#​106528](NousResearch/hermes-agent#106528)), Telegram quick setup with QR ported from the dashboard ([#​107242](NousResearch/hermes-agent#107242)), and \~60 more Desktop fixes largely from [@​OutThisLife](https://github.com/OutThisLife) and [@​kshitijk4poor](https://github.com/kshitijk4poor). **Cron & Kanban** - An off-tick "run now" no longer cancels the next scheduled run ([#​106306](NousResearch/hermes-agent#106306)); a killed manual run no longer blocks the next one for 5 minutes ([#​106733](NousResearch/hermes-agent#106733)); a one-shot changed to recurring keeps firing ([#​106532](NousResearch/hermes-agent#106532)); unpinned jobs run on their creation-snapshot model ([#​106499](NousResearch/hermes-agent#106499)); `--clone-all` no longer copies cron jobs ([#​106478](NousResearch/hermes-agent#106478)); `kanban promote` refuses undone parents ([#​106550](NousResearch/hermes-agent#106550)); `kanban_request_review` rejects unknown reviewer profiles ([#​106547](NousResearch/hermes-agent#106547)). **Tools & memory** - A stdio MCP server dying mid-call no longer replays the tool call ([#​106546](NousResearch/hermes-agent#106546)); a skills-only background review can no longer delete memory entries ([#​106310](NousResearch/hermes-agent#106310)); mem0 memory no longer drops long turns ([#​106542](NousResearch/hermes-agent#106542)); `tool_search` returns nothing rather than five tools sharing one word ([#​106676](NousResearch/hermes-agent#106676)); remote NOPASSWD sudo no longer prompts ([#​107939](NousResearch/hermes-agent#107939)); RSS and Reddit reading no longer activate by default ([#​105873](NousResearch/hermes-agent#105873)). **Housekeeping** - `config.yaml` backups live in one bounded `backups/config/` dir ([#​106388](NousResearch/hermes-agent#106388)); `hermes backup` keeps the newest 3 zips ([#​106455](NousResearch/hermes-agent#106455)); `hermes setup --reset` backs up the real config ([#​106453](NousResearch/hermes-agent#106453)); `debug share` retention shrunk to 1 day on the dpaste fallback ([#​106531](NousResearch/hermes-agent#106531)). ##### 👥 Contributors Thank you to the **140 contributors** whose commits, co-author trailers, and salvaged PRs landed in this window. **state.db campaign — salvaged PR authors:** [@​RikETS](https://github.com/RikETS), [@​JoaoMarcos44](https://github.com/JoaoMarcos44), [@​Halldrix](https://github.com/Halldrix), [@​TaoMasterCoder](https://github.com/TaoMasterCoder), [@​chelsealong](https://github.com/chelsealong), [@​ca-shrimp](https://github.com/ca-shrimp), [@​gaoanze888](https://github.com/gaoanze888), [@​nikkoxgonzales](https://github.com/nikkoxgonzales), [@​QDung210](https://github.com/QDung210), [@​fangliquanflq](https://github.com/fangliquanflq), [@​Sahilvishnaliya](https://github.com/Sahilvishnaliya), [@​kshitijk4poor](https://github.com/kshitijk4poor), [@​jonpol01](https://github.com/jonpol01), [@​HexLab98](https://github.com/HexLab98), [@​Sora-bluesky](https://github.com/Sora-bluesky), [@​Xipong](https://github.com/Xipong), [@​efe-arv](https://github.com/efe-arv), [@​liuhao1024](https://github.com/liuhao1024), [@​mssteuer](https://github.com/mssteuer), [@​Mi55ed](https://github.com/Mi55ed), [@​SulthanZahran1](https://github.com/SulthanZahran1), [@​Finn763](https://github.com/Finn763), [@​nftpoetrist](https://github.com/nftpoetrist), [@​jangomango76](https://github.com/jangomango76), [@​leegunwoo98](https://github.com/leegunwoo98), [@​ggoldani](https://github.com/ggoldani). **state.db campaign — issue reporters** (the forensics in these threads were often better than the fixes): [@​thedigitalcarpenterdad](https://github.com/thedigitalcarpenterdad), [@​Rroven](https://github.com/Rroven), [@​aoeman84](https://github.com/aoeman84), [@​StephanRosin](https://github.com/StephanRosin), [@​rubensandrade-sketch](https://github.com/rubensandrade-sketch), [@​wanliqin](https://github.com/wanliqin), [@​chenzheshushi-commits](https://github.com/chenzheshushi-commits), [@​CarlosReyesPena](https://github.com/CarlosReyesPena), [@​revazone](https://github.com/revazone), [@​reservassai-art](https://github.com/reservassai-art), [@​Cuttingwater](https://github.com/Cuttingwater), [@​soroush5](https://github.com/soroush5), [@​e-shizz](https://github.com/e-shizz), [@​shobhit-87labs](https://github.com/shobhit-87labs), [@​shivanathd](https://github.com/shivanathd), [@​hoelzl](https://github.com/hoelzl), [@​i8ei](https://github.com/i8ei), [@​Ace-Kelly](https://github.com/Ace-Kelly), [@​YinsenWANG](https://github.com/YinsenWANG), [@​zbabiarz](https://github.com/zbabiarz), [@​Sravanjangam](https://github.com/Sravanjangam), [@​0gl20shk0sbt36](https://github.com/0gl20shk0sbt36), [@​RChina](https://github.com/RChina), [@​bronder](https://github.com/bronder), [@​ccwssy](https://github.com/ccwssy), [@​bottenbenny](https://github.com/bottenbenny), and [@​Hitman117890](https://github.com/Hitman117890) whose Discord report kicked the campaign off. **Everyone in the window (alphabetical):** [@​0genlab](https://github.com/0genlab), [@​0xalydev](https://github.com/0xalydev), [@​100yenadmin](https://github.com/100yenadmin), [@​1052326311](https://github.com/1052326311), [@​686f6c61](https://github.com/686f6c61), [@​69k4xmdfm2-blip](https://github.com/69k4xmdfm2-blip), [@​abundantbeing](https://github.com/abundantbeing), [@​Adolanium](https://github.com/Adolanium), [@​Ahmett101](https://github.com/Ahmett101), [@​albert748](https://github.com/albert748), [@​AlexxRussell](https://github.com/AlexxRussell), [@​alt-glitch](https://github.com/alt-glitch), [@​auroracapital](https://github.com/auroracapital), [@​austinpickett](https://github.com/austinpickett), [@​babatorik](https://github.com/babatorik), [@​Bartok9](https://github.com/Bartok9), [@​benbarclay](https://github.com/benbarclay), [@​bennybuoy](https://github.com/bennybuoy), [@​brian717](https://github.com/brian717), [@​briandevans](https://github.com/briandevans), [@​buihongduc132](https://github.com/buihongduc132), [@​ca-shrimp](https://github.com/ca-shrimp), [@​cervantesh](https://github.com/cervantesh), [@​Cesar-Azeredo](https://github.com/Cesar-Azeredo), [@​ChanPark03](https://github.com/ChanPark03), [@​chelsealong](https://github.com/chelsealong), [@​ckomma](https://github.com/ckomma), [@​ClintonEmok](https://github.com/ClintonEmok), [@​crazyief](https://github.com/crazyief), [@​ctaylor86](https://github.com/ctaylor86), [@​dalzio](https://github.com/dalzio), [@​DavidMetcalfe](https://github.com/DavidMetcalfe), [@​Drexuxux](https://github.com/Drexuxux), [@​edosulai](https://github.com/edosulai), [@​efe-arv](https://github.com/efe-arv), [@​emozilla](https://github.com/emozilla), [@​ericmaddox](https://github.com/ericmaddox), [@​erosika](https://github.com/erosika), [@​ethernet8023](https://github.com/ethernet8023), [@​everm1nd](https://github.com/everm1nd), [@​FalconOrtiz](https://github.com/FalconOrtiz), [@​fangliquanflq](https://github.com/fangliquanflq), [@​Finn763](https://github.com/Finn763), [@​FirmamentalSpring](https://github.com/FirmamentalSpring), [@​francip](https://github.com/francip), [@​g3org3yo](https://github.com/g3org3yo), [@​gaoanze888](https://github.com/gaoanze888), [@​ggoldani](https://github.com/ggoldani), [@​Halldrix](https://github.com/Halldrix), [@​haydster7](https://github.com/haydster7), [@​hbizi](https://github.com/hbizi), [@​helix4u](https://github.com/helix4u), [@​HexLab98](https://github.com/HexLab98), [@​huklaa](https://github.com/huklaa), [@​IAvecilla](https://github.com/IAvecilla), [@​infinitycrew39](https://github.com/infinitycrew39), [@​jahfaliabdulrahman-dev](https://github.com/jahfaliabdulrahman-dev), [@​jangomango76](https://github.com/jangomango76), [@​JoaoMarcos44](https://github.com/JoaoMarcos44), [@​jonpol01](https://github.com/jonpol01), [@​jwilson411](https://github.com/jwilson411), [@​KeyArgo](https://github.com/KeyArgo), [@​kokhlo](https://github.com/kokhlo), [@​KoNit-K](https://github.com/KoNit-K), [@​kshitijk4poor](https://github.com/kshitijk4poor), [@​kyssta-exe](https://github.com/kyssta-exe), [@​leegunwoo98](https://github.com/leegunwoo98), [@​lesterlxt](https://github.com/lesterlxt), [@​liuhao1024](https://github.com/liuhao1024), [@​Mabolla](https://github.com/Mabolla), [@​manuelschipper](https://github.com/manuelschipper), [@​MaxFreedomPollard](https://github.com/MaxFreedomPollard), [@​mearls0501](https://github.com/mearls0501), [@​mengyuyuan](https://github.com/mengyuyuan), [@​Mi55ed](https://github.com/Mi55ed), [@​MiseHinoha](https://github.com/MiseHinoha), [@​mjshorty](https://github.com/mjshorty), [@​mkrb84](https://github.com/mkrb84), [@​moisesvalero](https://github.com/moisesvalero), [@​moken627-hub](https://github.com/moken627-hub), [@​mssteuer](https://github.com/mssteuer), [@​nateEc](https://github.com/nateEc), [@​nftpoetrist](https://github.com/nftpoetrist), [@​nickseelert](https://github.com/nickseelert), [@​nikkoxgonzales](https://github.com/nikkoxgonzales), [@​notwitcheer](https://github.com/notwitcheer), [@​onuraycicek](https://github.com/onuraycicek), [@​outdog-hwh](https://github.com/outdog-hwh), [@​OutThisLife](https://github.com/OutThisLife), [@​philmossman](https://github.com/philmossman), [@​phuongvm](https://github.com/phuongvm), [@​pierrenode](https://github.com/pierrenode), [@​portavales](https://github.com/portavales), [@​PRATHAMESH75](https://github.com/PRATHAMESH75), [@​QDung210](https://github.com/QDung210), [@​rewbs](https://github.com/rewbs), [@​RikETS](https://github.com/RikETS), [@​romanovzky](https://github.com/romanovzky), [@​ryantuc](https://github.com/ryantuc), [@​Sahilvishnaliya](https://github.com/Sahilvishnaliya), [@​salch-cred](https://github.com/salch-cred), [@​sgarrand](https://github.com/sgarrand), [@​shannonsands](https://github.com/shannonsands), [@​simpolism](https://github.com/simpolism), [@​Solitud1nem](https://github.com/Solitud1nem), [@​somewheresy](https://github.com/somewheresy), [@​Sora-bluesky](https://github.com/Sora-bluesky), [@​sprmn24](https://github.com/sprmn24), [@​squevo](https://github.com/squevo), [@​StellarisW](https://github.com/StellarisW), [@​Stoltemberg](https://github.com/Stoltemberg), [@​SulthanZahran1](https://github.com/SulthanZahran1), [@​Svector-anu](https://github.com/Svector-anu), [@​szicely](https://github.com/szicely), [@​TaoMasterCoder](https://github.com/TaoMasterCoder), [@​teknium1](https://github.com/teknium1), [@​ten82e](https://github.com/ten82e), [@​thedavidweng](https://github.com/thedavidweng), [@​tkaufmann](https://github.com/tkaufmann), [@​Totoro-qaq](https://github.com/Totoro-qaq), [@​Tranquil-Flow](https://github.com/Tranquil-Flow), [@​tuancookiez-hub](https://github.com/tuancookiez-hub), [@​TurgutKural](https://github.com/TurgutKural), [@​ugoenyioha](https://github.com/ugoenyioha), [@​unsupportedpastels](https://github.com/unsupportedpastels), [@​victor-kyriazakos](https://github.com/victor-kyriazakos), [@​webtecnica](https://github.com/webtecnica), [@​wliu-dev](https://github.com/wliu-dev), [@​wukangcheng1994](https://github.com/wukangcheng1994), [@​Xipong](https://github.com/Xipong), [@​Xixiartemis](https://github.com/Xixiartemis), [@​xkam7ar](https://github.com/xkam7ar), [@​xxxigm](https://github.com/xxxigm), [@​yavarb](https://github.com/yavarb), [@​yoniebans](https://github.com/yoniebans), [@​Youssef](https://github.com/Youssef), [@​yoyodine-industries](https://github.com/yoyodine-industries), [@​yuanchenglu](https://github.com/yuanchenglu), [@​YuhGuan](https://github.com/YuhGuan), [@​Zeus-Deus](https://github.com/Zeus-Deus). Also: Youssef. ##### Updating - Existing install: `hermes update` - Fresh install: `curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash` - Managed deployments should update through their deployment tooling using the new tag. - If your `state.db` was already damaged by 0.21.0/0.21.1: run `hermes doctor` first; it now names structural vs index damage correctly and points at `hermes sessions recover --inspect-only` (profile-pinned) when a rebuild isn't enough. **Full Changelog:** [v2026.9.7...v2026.9.11](NousResearch/hermes-agent@v2026.9.7...v2026.9.11) </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Berlin) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC42NS4wIiwidXBkYXRlZEluVmVyIjoiNDQuNjUuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUvY29udGFpbmVyIiwidHlwZS9wYXRjaCJdfQ==--> Reviewed-on: https://git.xcd.dev/gabrielcosi/home-ops/pulls/754
alt-glitch
added a commit
that referenced
this pull request
Sep 13, 2026
… 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 44 -> 45 (renumbered when folded into #109517; main was already at 44) 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.
alt-glitch
added a commit
that referenced
this pull request
Sep 13, 2026
… 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 44 -> 45 (renumbered when folded into #109517; main was already at 44) 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.
alt-glitch
added a commit
that referenced
this pull request
Sep 14, 2026
… 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 44 -> 45 (renumbered when folded into #109517; main was already at 44) 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.
JavierIslas
pushed a commit
to JavierIslas/hermes-agent
that referenced
this pull request
Sep 14, 2026
Cuarto merge mayor de upstream: 802 commits desde 6504f33 (integracion 2026-09-07). Forecast merge-tree: un solo conflicto. Decisiones de resolucion: - website/docs/developer-guide/smoke-test-fase7.md — conflicto de reubicacion (clase: superseded): el fork agrego docs/smoke-test-fase7.md y upstream renombro docs/ -> website/docs/developer-guide/. Contenido del fork conservado intacto en la ubicacion nueva. Cero referencias al path viejo en el arbol. - tests/tools/test_connector_bridge_wiring.py y tests/tools/test_connector_dispatch_policy.py (nuevos de upstream, NousResearch#106842) — migrados al contrato tri-tuple del fork: los mocks de _dispatch_pre_tool_call_hooks devuelven (block_msg, modified_args, halt_loop). Misma migracion que el merge 2026-08-17. Checklist del delta permanente verificado en el arbol mergeado: tri-tuple en plugins.py:1897, D5 halt en tool_executor (_dispatch_authorized_once) e invoke_tool, worker_mode en agent_init (_PASSTHROUGH_PARAMS) + system_prompt (_identity_parts worker-first, NousResearch#50233 intacto en la rama soul), load_worker_md re-export en run_agent, worker_mode: False en config_defaults, PROJECT_ROOT, comentario D8 converged en lifecycle_guard. Verificacion: suites del arnes 152/152 + 608/611 -> 611/611 tras la migracion (runner canonico, workers=2); ruff limpio in los seams; ty advisory con los mismos 28 diagnostics que upstream puro (preexistentes). Nota: _pre_tool_block_message (agent_runtime_helpers.py:2211) quedo orfana (unpack 2-tuple, sin llamadores) ya en el main del fork pre-merge; estado preexistente, no regresion de este merge. No se toco (sin drive-by edits).
jerome-benoit
pushed a commit
to jerome-benoit/hermes-agent
that referenced
this pull request
Sep 14, 2026
…ema (NS-867, PR1) (NousResearch#109517) * feat(connections): manage_connections covers local MCP servers; setup_mcp leaves the schema One model tool now connects the user to apps of both kinds. A target `{"name": "linear", "mcp": true}` is a locally configured MCP server; `install` / `enable` / `authorize` are its verbs. Bare strings and `{"name": ...}` stay managed connectors and that leg is unchanged. MCP targets run through one backend-owned connection operation (tools/connections_tool_operation.py): created with a server-side deadline from the new config key `connections.wait_timeout_seconds` (default 120, floor 5, no ceiling), per-target state, and exactly-once settlement (all resolved / Continue / deadline / interrupt). Unresolved targets freeze as `not_connected` with the settle reason. Why the fold works now: the approval card is reached through `agent.connection_callback` via the agent-level inline executor table, which is the only path that carries a GUI callback. Registry dispatch (every non-GUI surface) settles MCP targets as `unavailable` with the `hermes mcp install / login` hint; managed targets in the same call are unaffected. `setup_mcp` is removed from every advertised toolset and from the deferral list; an inline-table shim keeps calls from conversations opened before this change dispatching (prompt-cache protection). `_LEGACY_TOOL_ALIASES` is not the mechanism: inline tools bypass it. Gateway: `mcp.setup.request/respond` are replaced by `connection.request/respond/expire` (no wire compat; desktop ships with this). The bridge waits exactly the operation's deadline. The `session.resume` snapshot gains `pending_connection` so a reopened window restores the card with the original deadline. `manage_connections` joins `_SEQUENTIAL_DEADLINE_EXEMPT_TOOLS`: the operation owns its wait; the 420s guard must not report `tool_timeout` while the card is live. The portal `check_fn` on the tool is dropped in favour of a handler-level gate on the managed leg, so signed-out sessions can still approve local MCPs. * wip(desktop): connection.request store, resume restore, card routing for MCP targets Renderer half of the setup_mcp fold, first slice: connection-request store (mirrors clarify), connection.request/expire handling, pending_connection resume restore, mcpTargets() + isCardTool(name, args) so MCP-target manage_connections calls classify as cards. Not yet: the card component rewrite (mcp-setup-tool.tsx), mcp-directory.ts removal, vitest, docs. Does not typecheck until the card rewrite lands. * fix(config): hermes update turns on the connections toolset for saved toolset lists `hermes tools` writes an explicit `platform_toolsets.<platform>` list, and the resolver reads absence from that list as "unchecked". The `connections` toolset (NousResearch#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 44 -> 45 (renumbered when folded into NousResearch#109517; main was already at 44) 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. * refactor: anti-slop pass on the desktop slice; shorten added comments Parse connection.request at the boundary with a typed wire interface instead of unknown + typeof; mcpTargets reuses connectorText; comments cut to one or two lines. slop-ratchet: no net-new findings in 13 touched files. * feat(desktop): the MCP approval card answers manage_connections; MCP Directory removed The existing card (mcp-setup-tool.tsx) now reads the connection-request store, renders for manage_connections calls with mcp:true targets, answers through connection.respond with a per-target outcome, and no longer calls reload.mcp after Install; the new server's tools arrive on the between-turns refresh. A settled operation renders the first target's frozen state. session.resume restores a pending card with its original deadline on both the activate and cold-resume paths. lib/mcp-directory.ts is deleted along with its two fallback branches (suggestion provider, card install). The catalog was already primary in both; a catalog miss now yields no suggestion / a notInCatalog error. The GitHub never-suggest test is rewritten on catalog-shaped data. vitest: connection-request store (6), suggestion provider, clarify restore. slop-ratchet: no net-new findings in 19 touched files. * chore: drop __pycache__ files swept in by an over-broad git add * fix(desktop): correlate the connection.request row with the model's tool call by reason The synthetic row from connection.request and the tool.start row carried different ids and no shared match value (op_id is not in the model's args), so the card mounted twice. reason is the arg both sides carry. * docs: manage_connections covers local MCP servers; connections.wait_timeout_seconds * fix(connections): settle reason derives from target state, never from the renderer A card that answers one of two targets and claims all_resolved must settle as continue with the other target not_connected; found live with a two-target call. * fix(desktop): a pending connection card re-arms on resume and activate The store entry was restored but the transcript row was not, so navigating away and back (or reloading) lost the card while the backend kept waiting. restorePendingClarifyToolCall's core is generalized to any blocking tool name and both resume paths project the connection row through it. Verified live: card restored after navigate-away and after a full renderer reload, deadline_at unchanged, approve settles connected. * style: literal wording in added comments, docstrings and docs * fix: shared gateway-event contract and config-schema category for the connection events connection.request/expire replace mcp.setup.* in apps/shared gateway-events (json list, BACKEND_EVENT_NAMES, GatewayEventMap) so the renderer's event union includes them and the tui_gateway contract test passes. The new `connections` config section folds into the agent tab like the other single-field sections. * style: import order (perfectionist) in the desktop and shared files this PR touches * chore: retrigger CI (zero-job dispatch failure, auto-heal)
codeo1io
pushed a commit
to codeo1io/hermes-agent
that referenced
this pull request
Sep 19, 2026
…ema (NS-867, PR1) (NousResearch#109517) * feat(connections): manage_connections covers local MCP servers; setup_mcp leaves the schema One model tool now connects the user to apps of both kinds. A target `{"name": "linear", "mcp": true}` is a locally configured MCP server; `install` / `enable` / `authorize` are its verbs. Bare strings and `{"name": ...}` stay managed connectors and that leg is unchanged. MCP targets run through one backend-owned connection operation (tools/connections_tool_operation.py): created with a server-side deadline from the new config key `connections.wait_timeout_seconds` (default 120, floor 5, no ceiling), per-target state, and exactly-once settlement (all resolved / Continue / deadline / interrupt). Unresolved targets freeze as `not_connected` with the settle reason. Why the fold works now: the approval card is reached through `agent.connection_callback` via the agent-level inline executor table, which is the only path that carries a GUI callback. Registry dispatch (every non-GUI surface) settles MCP targets as `unavailable` with the `hermes mcp install / login` hint; managed targets in the same call are unaffected. `setup_mcp` is removed from every advertised toolset and from the deferral list; an inline-table shim keeps calls from conversations opened before this change dispatching (prompt-cache protection). `_LEGACY_TOOL_ALIASES` is not the mechanism: inline tools bypass it. Gateway: `mcp.setup.request/respond` are replaced by `connection.request/respond/expire` (no wire compat; desktop ships with this). The bridge waits exactly the operation's deadline. The `session.resume` snapshot gains `pending_connection` so a reopened window restores the card with the original deadline. `manage_connections` joins `_SEQUENTIAL_DEADLINE_EXEMPT_TOOLS`: the operation owns its wait; the 420s guard must not report `tool_timeout` while the card is live. The portal `check_fn` on the tool is dropped in favour of a handler-level gate on the managed leg, so signed-out sessions can still approve local MCPs. * wip(desktop): connection.request store, resume restore, card routing for MCP targets Renderer half of the setup_mcp fold, first slice: connection-request store (mirrors clarify), connection.request/expire handling, pending_connection resume restore, mcpTargets() + isCardTool(name, args) so MCP-target manage_connections calls classify as cards. Not yet: the card component rewrite (mcp-setup-tool.tsx), mcp-directory.ts removal, vitest, docs. Does not typecheck until the card rewrite lands. * fix(config): hermes update turns on the connections toolset for saved toolset lists `hermes tools` writes an explicit `platform_toolsets.<platform>` list, and the resolver reads absence from that list as "unchecked". The `connections` toolset (NousResearch#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 44 -> 45 (renumbered when folded into NousResearch#109517; main was already at 44) 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. * refactor: anti-slop pass on the desktop slice; shorten added comments Parse connection.request at the boundary with a typed wire interface instead of unknown + typeof; mcpTargets reuses connectorText; comments cut to one or two lines. slop-ratchet: no net-new findings in 13 touched files. * feat(desktop): the MCP approval card answers manage_connections; MCP Directory removed The existing card (mcp-setup-tool.tsx) now reads the connection-request store, renders for manage_connections calls with mcp:true targets, answers through connection.respond with a per-target outcome, and no longer calls reload.mcp after Install; the new server's tools arrive on the between-turns refresh. A settled operation renders the first target's frozen state. session.resume restores a pending card with its original deadline on both the activate and cold-resume paths. lib/mcp-directory.ts is deleted along with its two fallback branches (suggestion provider, card install). The catalog was already primary in both; a catalog miss now yields no suggestion / a notInCatalog error. The GitHub never-suggest test is rewritten on catalog-shaped data. vitest: connection-request store (6), suggestion provider, clarify restore. slop-ratchet: no net-new findings in 19 touched files. * chore: drop __pycache__ files swept in by an over-broad git add * fix(desktop): correlate the connection.request row with the model's tool call by reason The synthetic row from connection.request and the tool.start row carried different ids and no shared match value (op_id is not in the model's args), so the card mounted twice. reason is the arg both sides carry. * docs: manage_connections covers local MCP servers; connections.wait_timeout_seconds * fix(connections): settle reason derives from target state, never from the renderer A card that answers one of two targets and claims all_resolved must settle as continue with the other target not_connected; found live with a two-target call. * fix(desktop): a pending connection card re-arms on resume and activate The store entry was restored but the transcript row was not, so navigating away and back (or reloading) lost the card while the backend kept waiting. restorePendingClarifyToolCall's core is generalized to any blocking tool name and both resume paths project the connection row through it. Verified live: card restored after navigate-away and after a full renderer reload, deadline_at unchanged, approve settles connected. * style: literal wording in added comments, docstrings and docs * fix: shared gateway-event contract and config-schema category for the connection events connection.request/expire replace mcp.setup.* in apps/shared gateway-events (json list, BACKEND_EVENT_NAMES, GatewayEventMap) so the renderer's event union includes them and the tui_gateway contract test passes. The new `connections` config section folds into the agent tab like the other single-field sections. * style: import order (perfectionist) in the desktop and shared files this PR touches * chore: retrigger CI (zero-job dispatch failure, auto-heal)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hermes can use tools that run on a Nous-hosted server (draft a Gmail email, create a Linear issue, read a Notion page) when the user is signed in to the Nous Portal. It finds them through
tool_search, the same way it finds MCP tools. Signed out, or with the feature off, nothing changes.Stacked on #106676 (base
fix/tool-search-admission). A TUI follow-up sits on top: #106843.Before and after
maintool_searchreturns five unrelated local tools. The model says it cannot.tool_searchreturnsconnectors__gmail__SEND_EMAILandconnectors__gmail__CREATE_EMAIL_DRAFT. The model calls one.CONNECTION_REQUIREDwith a sign-in link.manage_connectionsgives the same link on request and can wait until the account connects.tools.connectors.enabled: false/stopduring a batch of connector callsGlossary
gmail,linear,notion. Connected or not, per user.connectors__<connector>__<tool>, soconnectors__gmail__CREATE_EMAIL_DRAFT. Never registered locally.connector-gateway.nousresearch.com.main:tool_searchfinds,tool_describeloads a schema,tool_callruns. Deferred tools are reachable only through them.manage_connectionsstatus,connect,reconnect,wait. Present only when signed in and the feature is on.tools.connectors.enabledAND signed in with gateway access. Fails closed.The user's walk
stateDiagram-v2 direction LR [*] --> SignedOut SignedOut --> SignedIn: hermes auth login nous SignedIn --> ConnectStarted: manage_connections connect gmail ConnectStarted --> Connected: user opens the link in a browser Connected --> Found: tool_search "send gmail email" Found --> Described: tool_describe Described --> Called: tool_call CREATE_EMAIL_DRAFT Called --> Connected: draft id returned SignedIn --> SignedOut: hermes auth logout note right of SignedOut manage_connections absent tool_search: local only end note note right of Connected manage_connections present tool_search: local + gateway end noteEvery arrow above was walked live in the TUI on 2026-09-10 against production; captures below.
The walk in the TUI, one capture per state
Real
hermes --tuisessions against production, 2026-09-10. Each shows the prompt, the tool calls the agent made, and its reply.0. Signed out. No
manage_connections;tool_searchfor a gmail tool returns nothing in 0.1 s, local only.1. Signed in, nothing connected.
manage_connections status.2. Connect started.
manage_connections connect notion; the agent tells the user a link is ready. The URL is withheld on purpose.3. Connected. gmail active.
4. Search.
tool_searchreturns the two gmail connector tools.5. Describe.
tool_describelists the parameters.6. Call. search, describe, call: draft
r-6633539535483257870created, not sent.7. Feature off.
tools.connectors.enabled: false: no gmail tool, nomanage_connections.8. Gateway dark.
CONNECTOR_GATEWAY_URLat a closed port: no gmail tool, no error shown to the user.How a connector tool is reached
sequenceDiagram participant M as Model participant B as Bridge (tool_search / describe / call) participant L as Local registry participant G as Connector gateway M->>B: tool_search(queries) par B->>L: BM25 over local tools and B->>G: POST v1/connectors/search G-->>B: hits + schemas, or nothing on any failure end B-->>M: one ranked list per query, both sources M->>B: tool_describe(names) B->>G: POST v1/connectors/schemas B-->>M: full parameters M->>B: tool_call(calls) loop one request per entry, in order B->>G: POST v1/connectors/execute G-->>B: result, or CONNECTION_REQUIRED + link end B-->>M: results in input orderConnector hits become catalog entries and rank in the same BM25 pass as local tools, so a 300-tool local catalog cannot crowd them out. Each
tool_callentry re-entershandle_function_callunder its own name, so hooks, middleware and approvals run per entry as for any tool. Any gateway failure (signed out, off, 404, timeout, bad shape) yields empty remote results and the local path runs as onmain.The availability gate
flowchart LR F["tools.connectors.enabled"] -->|true| S["signed in with gateway access"] F -->|false or error| OFF["off"] S -->|false or error| OFF S -->|true| ON["on"] ON --> A["manage_connections in the tool list"] ON --> B["tool_search: local + gateway"] ON --> C["tool_search description says what connectors__ names are"] OFF --> X["none of the above; identical to main"]The gate is evaluated once, when the agent is built. The three bridge tools keep their built bytes for the life of the conversation, so the prompt cache does not break if the portal blips mid-session. Availability is re-checked at dispatch only.
Settings
tools.connectors.enabledconfig.yamltrue. The off switch.connectionshermes toolsmanage_connections.CONNECTOR_GATEWAY_URL.env, optionalLive check
From a signed-in Hermes home, no model needed:
Expected with Gmail connected:
connectors__gmail__names first. Repeat withtools.connectors.enabled: false(local names only) and withCONNECTOR_GATEWAY_URL=http://127.0.0.1:9(local names only, no error).Result on 2026-09-10 against production: search found both gmail tools in 4.5 s among 307 local tools;
tool_callcreated draftr-7250075391723449971; off and dark arms returned local only.SEND_EMAILwas never called.Commits (14)
75d2cbd64ftools/tool_gateway/,tools/connections_tool.py,tools/managed_gateway_auth.py,model_tools_connectors.py, theconnectionstoolset, config defaults, tests.af7af54da9mainand made the feature dark.ed942e259a61ce518c511c88da61fetool_searchfacade intotools/connector_search.py.4c978ce0fatool_searchtakes at most 7 queries per call; the gateway returns 502 at 8.tool_describekeeps 10.c37a732583tool_searchdescription says whatconnectors__names are, only whenmanage_connectionsis in the session.c6d6e14117/stophalts a connector batch before the next remote call.18e357be305191d524510a35587f69502d7f5528normalize_tool_call_entriesmoves totool_search_validation.py.e97d4e7ab5bridge.py476 to 227 lines.5e6107d150Questions a reviewer will have
tool_searchqueries leave the machine?use_casestrings.tool_search?manage_connectionsappear or vanish mid-conversation?manage_connectionsin_HERMES_CORE_TOOLS?setup_mcpwill fold into it (NS-824). It ischeck_fn-gated, so it appears only when the gate passes.format_connector_namestrips theGMAIL_prefix, soGMAIL_FETCH_PROFILEand a literalFETCH_PROFILEon the same connector would collide. No such pair exists in the live catalog.5e6107d150: search keeps the twin the name reaches and logs a warning naming both slugs, so an alias can never be silent.disconnectreturns an error and the gateway sees no call.Tests
14 files, 259 tests, green at
e97d4e7ab5:scripts/run_tests.sh tests/tools/test_connect*.py tests/tools/test_tool_gateway*.py tests/tools/test_tool_search*.py tests/tools/test_managed_tool_gateway.py tests/tools/test_deferral_fixes.py tests/tools/test_refresh_agent_mcp_tools.py.Wide sweep,
tests/tools tests/agent tests/hermes_cli: this branch 24891 passed / 91 failed; cleanorigin/main24748 / 92. The same 21 files fail on both (approval, relay, update, MCP OAuth suites on this machine). One flaky browser test fails only on the branch and passes on rerun; the PR has no browser code.An independent Codex review returned 10 findings: 7 confirmed and fixed in the commits above, 2 refuted (bearer trust is an exact origin match; identifiers reach no privileged sink), 1 closed by
5e6107d150(the slug collision).Not in this PR
Free tier and guest identity (stay in hermes-magic). Desktop connector UI and the
setup_mcpfold (NS-824). Staging portal host. TUI bare-URL rendering (#106843). The portal gates connector routes to@nousresearch.comaccounts (NS-754); others see the "gateway dark" state.