Skip to content

Rebaseline ace/patches: exactly Nous upstream + VECTOR overlay (merge-commit only) - #21

Merged
vashkartik merged 5591 commits into
ace/patchesfrom
vector/rebaseline-merge
Aug 4, 2026
Merged

Rebaseline ace/patches: exactly Nous upstream + VECTOR overlay (merge-commit only)#21
vashkartik merged 5591 commits into
ace/patchesfrom
vector/rebaseline-merge

Conversation

@vashkartik

Copy link
Copy Markdown
Owner

The real fix

ace/patches becomes 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 0b17d4d7 to today's f5be9236e, so every future nightly git merge upstream/main conflicts 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)

  • Fork infra: .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-script HERMES_REPO_URL override.
  • Durable multi-client sessions ported onto upstream's refactored 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.
  • Built-in heartbeat (gateway/heartbeat.py + wiring + dashboard portfile).
  • Compacted-transcript visibility rewritten for upstream's _read_ctx connection model.
  • Codex app-server history threading and the macOS ps -AEww gateway-restart fix.
  • Branding: productName/title "VECTOR".

Dropped per "exactly Nous": all 58 fork desktop divergences, fork-only REST duplicates, the clarify timeout=None fork (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

  • Upstream's own test_tui_gateway_server.py: 517/517 green against the merged tree.
  • 22 focused test files green per-file, including both E2Es (real gateway, two WS clients, reconnect/replay; update-drain clarify survival).
  • ruff clean. 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

Vansh5632 and others added 30 commits August 3, 2026 14:24
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.
kshitijk4poor and others added 13 commits August 4, 2026 14:34
- 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>
@vashkartik
vashkartik requested a review from vectorcmd as a code owner August 4, 2026 11:36
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
vashkartik force-pushed the vector/rebaseline-merge branch from 66a6853 to 09d2170 Compare August 4, 2026 11:38
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vashkartik
vashkartik force-pushed the vector/rebaseline-merge branch from 09d2170 to d151c6d Compare August 4, 2026 11:40
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 vashkartik added the ci-reviewed CI-sensitive workflow changes reviewed label Aug 4, 2026
@vashkartik
vashkartik force-pushed the vector/rebaseline-merge branch from d151c6d to 1d3638d Compare August 4, 2026 11:51
vectorcmd and others added 2 commits August 4, 2026 08:07
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
vashkartik force-pushed the vector/rebaseline-merge branch from 1d3638d to 68d6e35 Compare August 4, 2026 12:07

@vectorcmd vectorcmd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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
vashkartik merged commit f0b6ab1 into ace/patches Aug 4, 2026
49 checks passed
@vashkartik
vashkartik deleted the vector/rebaseline-merge branch August 4, 2026 12:19
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-reviewed CI-sensitive workflow changes reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.