Rebaseline ace/patches: exactly Nous upstream + VECTOR overlay (merge-commit only) - #21
Merged
Conversation
Stores per-server tool manifests in ~/.hermes/mcp_schema_cache.json so tools can be registered into the agent snapshot without spawning the stdio child at startup. Entries are keyed by server name plus a fingerprint of the connection-defining config (command/args/url/ transport/tool filters), so any config change invalidates the entry. Extracted from NousResearch#56832.
…earch#56832) Wires the fingerprint-keyed schema cache (previous commit, @Vansh5632's design from NousResearch#56832) into the startup path, re-derived onto main's current connect machinery: - register_mcp_servers: servers with mcp_servers.<name>.lazy=true whose config fingerprint matches a valid cache entry register tools from cache WITHOUT spawning; miss/stale falls back to eager connect. - First tool use routes through _ensure_lazy_server_connected, which composes with the connect cooldown (NousResearch#50394) and _server_connecting dedup rather than duplicating the connect path. - resource/prompt utility handlers (list_resources/get_prompt) also connect-on-first-use — closes the gap flagged in the original sweeper review. - Write-through: a live connect refreshes the cache entry. Config gate is per-server, default OFF, matching the idle_timeout_seconds key pattern. 24 lazy/cache tests + 440 mcp-wide green; mutation-checked (cache-read disabled -> registration test fails; connect bypassed -> 3 first-use tests fail).
Five review findings folded: - schema cache writes via utils.atomic_json_write (fsync; was bare tmp+replace), file moved to cache/mcp_schema_cache.json with 0o600 (sibling precedent: registry discovery cache) - phantom-tool reconciliation: after a lazy server's first-use connect, cached tools the live server no longer offers are deregistered (were permanent registry ghosts burning circuit-breaker strikes on every 'Unknown tool' round-trip); stale fingerprint logged - cache-load path now runs _scan_mcp_description like the eager path (cache file is user-writable JSON; defense-in-depth) - write-through skips the disk rewrite when the entry is unchanged (a flapping stdio server was rewriting byte-identical JSON per revival) - _lazy_server_fingerprints no longer write-only dead state (consumed by the reconciliation logging) 444 mcp tests green (440 pre-fold + 4 new guards); phantom-dereg and write-skip mutation-checked.
…NousResearch#74178) build_local_transcribe_kwargs read stt.local.no_speech_prob_threshold / stt.local.logprob_threshold only for Hermes' post-filter (_is_hallucinated_segment). faster-whisper's model.transcribe() never received them, so its internal defaults (no_speech_threshold=0.6, log_prob_threshold=-1.0) always applied and silently dropped low-confidence segments before they reached the post-filter — making those config knobs dead for the first gate. Non-English speech decodes at a lower avg_logprob, so the English-tuned defaults discard whole utterances (empty transcript despite correct capture and language detection). Map the same config values through to model.transcribe() so both gates stay in sync and the knobs work. Defaults are unchanged, so behavior is identical unless a user tunes them. Fixes NousResearch#74178
chore: add contributor email mapping for wangyunyou
The copilot branch of _seed_from_singletons ran the suppression gate _after get_copilot_api_token(), which retries the network exchange 3x with backoff (~13s worst case). A source the user already suppressed (hermes auth remove copilot gh_cli) still burned the full exchange dead time on every pool load — model picker open, /model, agent startup — only to have the entry discarded afterwards. Move the _is_suppressed() gate ahead of the network call, matching the early-gate pattern every other singleton branch uses. Suppressed copilot sources now skip the exchange entirely. Measured: model.options payload build drops from ~13s to ~0.2-0.4s for a user with copilot suppressed. Add regression test test_load_pool_skips_exchange_for_suppressed_copilot asserting the exchange is never invoked for a suppressed source.
…ppressed The all-sources suppression gate now runs before resolve_copilot_token(), which shells out to `gh auth token` (~30ms) on every pool load. A user who suppressed every copilot source (hermes auth remove copilot gh_cli suppresses gh_cli + all env variants) still paid the subprocess spawn on every load — model picker open, /model, agent startup. Enumerate the same source space credential_sources._remove_copilot_gh suppresses and bail before any work when all are suppressed. Measured: model.options payload build drops from ~0.46s to ~0.26s cold for an all-suppressed user; resolve_copilot_token() is no longer called at all.
Review fold on the NousResearch#76341 salvage: the substring test ('gh' in source.lower()) classified GH_TOKEN and GITHUB_TOKEN as gh_cli, so a user's env-var-specific suppression was silently bypassed (and suppressing gh_cli silently dropped env tokens). Pre-existing bug on main, but the PR's early gate makes the classification decide whether the exchange runs at all. Match resolve_copilot_token's exact 'gh auth token' sentinel instead. Adds 3 regression tests: env-var suppression gates the exchange, gh_cli suppression doesn't swallow env tokens, all-sources suppression skips the resolve subprocess entirely. Also corrects the ~13s comment (actual worst case ~35s: 3x10s timeouts + 4.5s backoff).
chore: add contributor email mapping for szzhoujiarui
…-rodboev-maarten chore: contributor email mappings for rodboev and MaartenDMT
…-endeavoryen chore: add EndeavorYen to AUTHOR_MAP
_adapter_config_interactive() imported get_env_var and set_env_var from
hermes_cli.config, but these do not exist — the actual functions are
get_env_value and save_env_value. This caused an ImportError at runtime,
breaking the entire LINE platform adapter setup.
Pain before: Any user who ran the LINE adapter setup function would get:
ImportError: cannot import name 'get_env_var' from 'hermes_cli.config'
Fix: Import the correct functions with aliased local names:
from hermes_cli.config import get_env_value as _get_env, save_env_value as _set_env
Also fixed an indentation bug introduced during the fix: the 'if value: _set_env()'
block was incorrectly nested inside the except clause.
PR: N32 (hermes-agent audit)
…Research#77600) Needed for the NousResearch#59077 salvage (batch compression-tip row fetch) so release attribution resolves the contributor's commits.
…-light-merlin-dark chore: add light-merlin-dark to AUTHOR_MAP
Empty content retries previously fired back-to-back with no delay, wasting up to 3 rapid API calls, and could not be cancelled mid-wait. Apply the same jittered_backoff() already used for rate-limit and API-error retries, sleeping in small increments so a user interrupt aborts the wait instead of blocking until it elapses. Fixes NousResearch#35230
The retry loop gates on real time.time() < sleep_end; with sleep mocked to a no-op the test hot-spun 7.5 wall-clock seconds. Advance a fake clock by each sleep amount instead (pattern precedent: test_session_activity_persist.py).
The existing test only mutated the miss-path return; a mutation to 'return _PROVIDER_LIST_CACHE' (aliasing the global cache) survived the suite. One line pins the cached-return copy. Mutation-checked.
The runtime footer (`/footer`) shows what model ran and how full the context
is, but not how long the turn took. On a messaging platform there is no
progress bar and no shell timer — a turn that took 4 seconds and one that took
four minutes produce visually identical replies. Users comparing models,
providers, or reasoning levels have no at-a-glance signal for the one
dimension they most often care about, and "was that slow or did I imagine it?"
is unanswerable after the fact.
Adds a `latency` field to the existing footer machinery, rendering the
wall-clock duration of the agent run: `<1s`, `22s`, `1m05s`.
`gateway/run.py` measures with `time.monotonic()` immediately around the
`self._run_agent(...)` await in `_handle_message_with_agent` — the same
function that already builds the footer, so the value is the user-perceived
turn duration (monotonic, so it is immune to wall-clock/NTP adjustment).
`latency` is deliberately NOT in `_DEFAULT_FIELDS`. It is opt-in via
`display.runtime_footer.fields`. Every existing footer — and every footer a
user has today without touching config — renders byte-identically.
This is enforced by tests, not just asserted:
- `test_latency_not_in_default_fields` pins the default tuple.
- `test_resolve_footer_config_default_fields_exclude_latency` pins what
config resolution produces for an untouched config.
- `test_default_footer_renders_byte_identically` pins five exact output
strings for default-config renders **while supplying `turn_seconds`** —
proving that even when the caller measures timing, a default-configured
footer does not show it.
- `test_default_build_footer_line_ignores_turn_seconds` asserts
`build_footer_line(...) == build_footer_line(..., turn_seconds=125.0)`
under default fields.
Adding `latency` to `_DEFAULT_FIELDS` fails 11 of these tests.
No new config surface (reuses `display.runtime_footer.fields`), no new env
vars, no new core tool, no new model-facing schema. One new module-private
helper (`_format_latency`), one new keyword argument threaded through the two
existing footer functions, and 3 lines in `gateway/run.py`.
`turn_seconds` defaults to `None` and the field is skipped when it is `None`
or negative, so any call site that does not measure timing keeps working
unchanged.
`tests/gateway/test_runtime_footer.py` (+185): `_format_latency` boundary
table (sub-second, rounding at 59.4/59.6, the `m{:02d}s` zero-pad, 60m), the
render/skip/opt-in matrix, field-order placement, `build_footer_line`
threading, and the byte-stability block above.
RED-proved by mutation — each of these breaks tests:
- `latency` added to `_DEFAULT_FIELDS` → 11 failures
- dropping the `turn_seconds is not None and >= 0` guard → 2 failures
- `{sec:02d}` → `{sec}` → 6 failures
- `build_footer_line` not threading `turn_seconds` → 1 failure
51 passed in `tests/gateway/test_runtime_footer.py`; 54 passed across the
footer blast radius. `ruff check` clean.
Review follow-up on the NousResearch#56798 salvage: the gate shipped fully dormant (no provider profile sets supports_prompt_cache_key, no production caller passes it, and no plain 'openai' profile exists to set it on) — AGENTS.md rejects dead code wired in without E2E proof. Activate the one endpoint where the field is first-class: exact-host api.openai.com (OpenAI documents prompt_cache_key; GPT-5.6+ docs recommend it for cache routing). Deliberately NOT substring matching — Azure/OpenAI-compat endpoints may reject unknown fields and stay opt-in via the flag. 4 new tests (imply + 3 spoof/proxy/Azure negatives); mutation-checked (substring-weakened host check fails the spoof tests).
Re-derivation of PR NousResearch#65645 onto current main: _build_gateway_agent_history already runs inside a turn whose config was loaded once into ctx.user_config; re-reading config from disk via _load_gateway_config() per turn is redundant. Reuse the loaded turn config.
…earch#39267) Re-derivation of PR NousResearch#39399 onto current main: pass --prefer-offline to the web-UI workspace install (both silent and verbose arms of _install_web_deps) and to the update-time Node dependency refresh in _update_node_dependencies, so npm reuses its local cache instead of re-fetching metadata. Test expectations updated to match, mirroring the PR's own test-update commit.
- write_file: encode content once, share bytes between bytes_written and the sha256 verification (drops a second full-content encode per write) - patch_parser: replace the except-TypeError retry around write_file(pre_content=...) with signature-based feature detection so a TypeError raised inside a capable implementation propagates instead of triggering a duplicate write; tests for both duck-typing contracts - tests: real-ops V4A BOM round-trip + _file_has_bom disk-probe guard (the teknium1-review regression previously only covered by a fake) - comment: document dirs_created's long-standing "parent ensured" meaning
requires-python is >=3.11 so tomllib is always in stdlib; the tomli fallback branch in _lint_toml_inproc was unreachable. Removes the dependency from pyproject.toml + uv.lock and deletes the dead try/except ImportError fallback in the code.
…warning NousResearch#75017: Telegram polling conflict retry used drop_pending_updates=False, starting a new getUpdates session that immediately got 409'd by the previous still-expiring session — creating the very conflict it was trying to recover from. Switch to drop_pending_updates=True so Telegram terminates stale sessions. Also add a recovery-generation guard so the first transient getUpdates success after a retry doesn't reset the conflict counter back to 0 (defense-in-depth from PR NousResearch#75096). NousResearch#75153: The WAL-reset warning always said 'hermes update can repair' even for git/pip/system Python installs where it can't. Now uses detect_install_method() + recommended_update_command_for_method() to give a context-appropriate hint (hermes update for git, docker pull for docker, nix message for nix, generic install hint as fallback).
…-junhohong chore: contributor email mapping for junhohong
Loopback dashboard tabs now share one one-shot stale-token recovery path across REST 401s, the PTY socket, the structured event socket, and the shared JSON-RPC gateway wrapper. The shared client exposes only an optional close-event interception hook; the dashboard remains responsible for deciding that loopback 4401 means reload. Constraint: Current main delegates the web gateway to apps/shared JsonRpcGatewayClient, and NousResearch#54022 review requires a shared-client-compatible close-code hook plus direct ChatSidebar event-socket coverage. Rejected: Restore the dashboard's old direct WebSocket implementation | stale against the shared JSON-RPC client and would duplicate transport behavior. Confidence: high Scope-risk: moderate Directive: Keep stale-token policy dashboard-specific; the shared JSON-RPC client should expose close events without learning dashboard auth semantics. Tested: npm --workspace web test (21 files, 106 tests); focused stale-token tests (5 files, 14 tests); npm --workspace web run typecheck; npm --workspace @hermes/shared run lint; npm --workspace @hermes/shared run typecheck; focused web eslint; git diff --check. Not-tested: Manual browser smoke test across a real dashboard restart.
react-router v7 exports MemoryRouter from 'react-router', not 'react-router-dom'. The test was written when the repo still imported from 'react-router-dom' (4000+ commits ago).
Sibling site missed by PR NousResearch#54022 — /api/console WebSocket in HermesConsoleModal.tsx has the same buildWsUrl → stale-token → 4401 close path as the PTY and events WebSockets. Without this guard, opening the console after a dashboard restart shows 'Console closed (4401). auth: token_mismatch' with no recovery.
When Grok runs on xAI Responses, only swap to native server-side web_search when the active/configured backend is xai. For Firecrawl and other Hermes providers, keep client dispatch under a renamed wire tool so Grok cannot hijack web_search and ignore user config.
Lock in backend preference, wire-name aliasing, and normalize mapping so configured non-xai search providers stay on the Hermes client path. Also init conflict-recovery generation on the telegram bare-adapter helper so CI polling progress tests do not AttributeError.
Drop the manual web.search_backend / web.backend config-reading block that duplicated _read_config_key in web_search_registry.py. The function now delegates directly to get_active_search_provider() (which reads the same config keys via the registry's canonical resolver) and falls back to _get_search_backend() only when the registry has no providers loaded. Also updates the TestXaiWebSearchBackendPreference tests to monkeypatch the registry instead of load_config_readonly, and adds two new tests for the legacy fallback path (no provider registered -> _get_search_backend).
Owner directive: the fork must BE NousResearch/hermes-agent, with only the VECTOR overlay on top. The old model — a 103-file patch surface squash-merged against a July baseline — left the nightly sync failing on 31-file conflict walls (~5,500 commits behind) and production running hand-edited, uncommitted hotfixes. This tree is upstream/main (f5be923) plus a deliberately small overlay: Fork infra (kept verbatim): .ace/ metadata (upstream-main.sha now records f5be923), CODEOWNERS, patches-guard workflow, contributor allowlist, hermes_cli/update_guard.py (the cross-process update-drain system the nightly apply pipeline drives) with its gateway/run.py + entry.py + web_server integration, and the HERMES_REPO_URL install-script override. Durable multi-client sessions (ported onto upstream's methods_* layout): tui_gateway/session_stream.py fan-out/replay/ledger, write_json delivery, ownership + request-id wiring in methods_prompt/methods_session, disconnect handoff, queued-work reaper exemptions + background sweeper, the session.subscribe/unsubscribe/claim/replay + request.status/prompt.status RPCs, clarify.pending, and the gateway.ready capabilities advertisement. Built-in health heartbeat: gateway/heartbeat.py + start_gateway wiring + the dashboard portfile writer. Compacted-transcript visibility: include_compacted reads in hermes_state (rewritten for upstream's _read_ctx connection model), the sessions router, api_server, and compute_host. Codex app-server conversation_history threading and the macOS ps -AEww profile-gateway scan fix. Branding: productName/title "VECTOR". Dropped, per "exactly Nous": all 58 fork desktop divergences, the fork's sessions-daily-stats and duplicated REST endpoints, the fork clarify timeout=None (upstream's configurable _clarify_timeout_seconds wins), the turn-end _persist_session_history rewrite (upstream's agent persists rows incrementally; upstream's tests forbid the extra replace), the superseded client_request_id bridge (request ledger provides wire compat), and whitespace-only patch noise. Verified: upstream's own test_tui_gateway_server suite 517/517 green against the merged tree; 22 focused test files green per-file including both E2Es (two-client durable session, update-drain clarify survival); ruff clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rebaseline drops all fork desktop divergences, including the guard script itself; the check stays (branch protection references it) and passes vacuously until a desktop patch surface exists again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vashkartik
force-pushed
the
vector/rebaseline-merge
branch
from
August 4, 2026 11:38
66a6853 to
09d2170
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vashkartik
force-pushed
the
vector/rebaseline-merge
branch
from
August 4, 2026 11:40
09d2170 to
d151c6d
Compare
Four upstream author emails lacked mapping files: AllardQuek, StanleyStetson (noreply forms), brinshadewater@gmail.com (BrinShadewater via GH email search), and the malformed b@b resolved to its commit's GitHub author (blavlabla) via the upstream commits API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vashkartik
force-pushed
the
vector/rebaseline-merge
branch
from
August 4, 2026 11:51
d151c6d to
1d3638d
Compare
A leaked test session with queued work let the 15s sweep write frames into whichever stdout the currently-running test had patched (upstream's write_json concurrency test caught an extra line on CI). The durability tests call _sweep_queued_prompts() directly, so coverage is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
True merge (not squash): the second parent carries upstream/main f5be923 into ace/patches ancestry, so every future nightly 'git merge upstream/main' computes its base at f5be923 instead of July's 0b17d4d — conflicts shrink from 31-file walls to whatever touches the small VECTOR overlay. Tree is identical to vector/nous-rebaseline (upstream + overlay); see that commit for the full inventory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vashkartik
force-pushed
the
vector/rebaseline-merge
branch
from
August 4, 2026 12:07
1d3638d to
68d6e35
Compare
vectorcmd
approved these changes
Aug 4, 2026
vectorcmd
left a comment
Collaborator
There was a problem hiding this comment.
Owner-authorized merge (established flow, PRs #17/#19/#20). Full CI green (43 checks) including the patch guard's desktop build + gateway regression suite on the rebaselined tree. Recorded as CODEOWNERS approval at the repo owner's instruction — not an independent human review. MUST be merged with a merge commit to preserve the upstream parent.
vashkartik
pushed a commit
that referenced
this pull request
Aug 4, 2026
- scripts/vector/apply_vector_orchestrator_config.py: idempotent, comment- preserving enablement of kanban/delegation/cron/web/media/computer-use/ voice on cli+telegram, incl. the top-level kanban toolsets gate and the no-unattended-audio invariants (auto_tts/beep/wake_word/voice_fx off). - scripts/vector/install_mp_skills.py: install mattpocock/skills from a pinned checkout into the profile as mp-*, prefixing dir AND frontmatter name (dir-only prefixing makes skill_view ambiguous), deduping against unprefixed and externally-vendored mp-* providers, with a zero-duplicate post-condition. - scripts/vector/provision-vector-runtime.sh + launchd plist template: profile-scoped 0.20 runtime staging (clone + uv sync + gateway job). - tests/test_compacted_transcript_visibility.py: behavioral coverage for the compacted-transcript display projection (active=1 OR compacted=1), previously untested — model view drops archives, display keeps them, rewound rows stay hidden, archives stay searchable. - docs/vector/herald-upgrade-runbook.md: version map, the state.db v25 mixed-version hazard (0.19 reader vs 0.20-migrated home), what was applied live pre-cutover, staging E2E evidence, cutover + rollback. Stacked on #21 (rebaseline to upstream 0.20.0 Herald + VECTOR overlay). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 4, 2026
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.
The real fix
ace/patchesbecomes exactly NousResearch/hermes-agent upstream main (f5be9236e, today) + the VECTOR overlay — ending the two-repo drift that produced 31-file nightly conflict walls and hand-edited production hotfixes.This PR must be merged with a MERGE COMMIT (not squash). The head commit is a true merge whose second parent is upstream/main — that's what permanently moves the merge-base from July's
0b17d4d7to today'sf5be9236e, so every future nightlygit merge upstream/mainconflicts only where upstream touches the small overlay. Squashing would flatten the parent and keep the old merge-base forever.The overlay (everything that isn't pure Nous)
.ace/(baseline sha →f5be9236e), CODEOWNERS, patches-guard workflow, contributor allowlist,hermes_cli/update_guard.py+ its gateway/entry/web_server integration (the nightly apply pipeline drives this), install-scriptHERMES_REPO_URLoverride.methods_*layout (upstream has no equivalent): session_stream fan-out/replay/request-ledger, ownership + retry-idempotency in prompt.submit, disconnect handoff, queued-work sweeper + reaper exemptions, subscribe/claim/replay/request.status/prompt.status/clarify.pending RPCs, capabilities advertisement._read_ctxconnection model.ps -AEwwgateway-restart fix.Dropped per "exactly Nous": all 58 fork desktop divergences, fork-only REST duplicates, the clarify
timeout=Nonefork (upstream's configurable timeout wins), the turn-end transcript rewrite (upstream's agent persists incrementally and upstream's tests forbid the extra write), the superseded client_request_id bridge (ledger keeps wire compat), whitespace noise.Verification
test_tui_gateway_server.py: 517/517 green against the merged tree.ruffclean. Dependency drift noted (0.19→0.20, cryptography 48, Pillow 12.3, new relay wheel): the live apply must run the venv sync.🤖 Generated with Claude Code