Repository navigation
chore: sync upstream NousResearch/hermes-agent main (pure sync) - #16
Merged
Merged
Conversation
…ease poll Round 5's Start-MarkerCustodian takes a killed hand-off script's claim over and releases it on its next 1 s poll after the update ends. Strict run 37393368977 read the marker ~2 s before that release (custodian removed it at 01:01:32) and called it a surviving marker. The cell now waits up to 30 s for the release; a genuinely stuck marker lives until the heartbeat ceiling, minutes.
…rols
tests-os[windows] imports only the files list_os_marked_tests.py emits and
keeps `-m "platforms and not integration"` tests its host admits. The replay
regression for the Windows interpreter layout (venv holding python.exe alone,
Git Bash) carried no platforms marker, so the Windows selector omitted the
whole file and green Windows jobs never ran it.
The replay test, the R15 dispatch chain and the disabled/advisory/no-command/
empty-selection controls now carry platforms("linux", "windows"): Linux keeps
its coverage, the Windows selector lists the file (194 -> 195 files) and the
Windows -m filter collects 35 of its nodeids. macOS is left out on purpose:
the replay has not been run on bash 3.2. A routing test pins both gates via
the real selector and its spec semantics.
test_crash_cells carried its own marker parser and identity rule, and it decides the orphaned_update verdict. It disagreed with the product on three inputs: a float started_at (product: malformed, copy: live), a create time 1.5 s off (product: live within its 2.0 s tolerance, copy: dead at 1.0 s) and a run: line before delegate: (product: delegate live, copy: dead, it read line 4 only). The self-check claimed parity but never called update_lock and used the test's own pid, which the product judges by exact incarnation. _marker_live now asks update_lock._parse_marker(...).owner_live() / delegate_live(); the crash cells run in a pytest process on the same Windows host as the marker's pids, so the product's process table reads them as the updater does. The self-check marks a separate live process with an integer started_at, compares the oracle to judge_marker, and adds the three rows. The ORPHAN_RELEASE_GRACE poll is unchanged.
…never reach Replaying the last 131 non-merge main commits (4787e4d) through the classifier: main selects e2e_upgrade 5 / e2e_desktop_update 3, the CI branch 12 / 10. Four of the seven new hits per lane touched only plugins_cmd.py or plugins_cmd_install.py. migrate_all_homes reaches them only when a home's configured memory provider left core (the catalog install); no upgrade or Desktop update journey configures one, so those suites cannot observe the change, and the unit lane already runs on every .py edit. Measured on a fresh home: memory_provider_migration, pm/plugins_state, agent/memory_provider and plugins/memory run; plugins_cmd_install executes only its module body, plugins_cmd is never imported. Kept, because a fresh-home update runs them: memory_provider_migration.py (migrate_all_homes on every source build), plugins/memory/__init__.py (the post-update Honcho profile sync), pm/install.py (sealed() / lazy_installs_allowed() gate every update's default-tool install), and scripts/build/ (frontend-common.mjs is imported by freshness/web/tui/desktop). After: 8 / 6. A guard test fails if the journey-home migration starts running plugin-install code, the signal to route those modules again.
… their siblings `hermes_cli/_early_recovery_zip.py` (split out of `_early_recovery.py` on the commit-point PR) is imported by the update path, so a change to it must start the update pipeline and install E2E suites. Both lists and the install-e2e path filter now match the `_early_recovery` prefix, which also routes any later sibling. `remote-lifecycle-v2-marker.test.ts` reads the update-marker corpus and is now a listed consumer; `remote-update-marker-programs.test.ts` exists in this tree, so its sibling-PR allowance is gone.
The replayed steps call git. On Linux os.defpath reaches it; on Windows the replay's PATH only reached it through the located bash's directory, and Git for Windows ships a bash.exe in usr\bin with no git.exe beside it. The Windows arm64 lane resolved that bash and 13 replay cases failed with "git: command not found" (1e547a8 had just put them on the native Windows lanes). git's own directory now rides along like bash's.
…ings' allowance Two ratchets this suite arms for its own siblings fired once they landed: - the Windows pause (NousResearch#132338) now imports gateway/status_inline_source.py to recognise inline-bootstrapped gateways, so a change there starts the e2e_upgrade suite like gateway/status.py (only two importers: not a hub) - the marker-corpus consumers NousResearch#132345 and NousResearch#132354 ship are on main, so _LANDS_IN_SIBLING_PR empties as its comment asks
…install code The journey-home guard counted every non-<module> frame in plugins_cmd*.py as the migration running plugin-install code. d6813a5 gave plugins_cmd_install.py its first class statement (_ConsentRefusal), whose body runs at import as a frame named after the class, so a plain import (via plugins_cmd.py's module-level import) read as a call and the guard failed with no change in what the update does: migrate_all_homes() still returns [] and calls nothing in the module. Frames called by their own file's <module> frame (class statements, module-level comprehensions) are import-time execution and are skipped; a real call into an install function is still recorded.
A split model was priced by its first shard alone, so the physics check and the residency cap admitted giants the card cannot hold: on a 256 GB box a 175 GB model priced as 6.7 GiB and the router booted with room for three residents, two of which then co-resided and OOM'd every session. Sum the tensor table, the host-side embedding duplicate and the per-block FFN map across every part on disk; architecture metadata still comes from the first part. The truncated FFN map also disabled spill placement, which is the lever that would have relieved the pressure. A part that cannot be parsed is priced out rather than fatal — refusing an incomplete split to serve remains staged_in's job.
… by all its parts A header cut short raised struct.error, which no caller of read_gguf_header catches (they skip a model on ValueError or OSError), so one truncated file escaped the whole preset pass. A short read now raises ValueError. The status row sized a side-loaded split by its first file, which can be a metadata stub of a few MB; it now sums every part on disk. The split fixture writes model metadata into part 1 only, as gguf-split does: a published Flash Next split carries 67 keys in part 1 and only its 3 split.* keys in parts 2-3.
…loaded llama.cpp leaves a tensor its architecture marks TENSOR_READ_LAZY in the file and reads its rows on demand; under the default --lazy-mode auto it does so for a marked tensor over 4 GiB (b11370: per_layer_token_embd in qwen4exp and gemma4). Hermes priced those tables as loaded weights, so Qwen3.8 Flash Next's 26.8 GiB per-layer embedding table counted against memory it never occupies. The reader records the candidate tensors per part and applies the architecture from part 1, so a table in a later split part is found. profile_from_gguf prices weights without them, and a catalog build carries lazy_bytes so the same holds before download.
…head Flash Next's catalog build moves from UD-Q4_K_XL (103.6 GiB, too big for a 128 GB unified-memory machine) to UD-IQ4_XS: 91.9 GiB on disk, ~60 GiB loaded once the per-layer embedding table stays on disk. The model has no MTP layers; unsloth ships the head as its own file, so the catalog gains an mtp_head companion. It downloads with the model, is priced before and after download, and only once it is on disk does the preset add --spec-type draft-mtp with the head as --model-draft (depth 3, from the vendor recipe). min_engine is b11370, the first pinned build with Qwen4Exp MTP. With the projector and head, Flash Next needs ~74 GiB at its native 256K window, so the derived recommendation now picks it on 96 GB discrete and on 96 and 128 GB unified memory. The catalog test admits 4-bit IQ4 builds as Q4-class, the pane's MTP flag covers a shipped head, and the Hugging Face browser stops listing mtp-*.gguf heads as models.
llama.cpp's --lazy-mode auto resolves to off on any device without mmap support, which covers every integrated GPU (b11370, NousResearch#28160). On a DGX Spark, Flash Next's 26.8 GiB per-layer embedding table therefore loaded in full (27.2 GiB resident) while Hermes priced it as left on disk. --lazy-mode on keeps it out (0.4 GiB resident) at the same decode speed and within 2% of the prefill speed on GB10. The preset now passes lazy-mode = on wherever Hermes prices the table as on disk, so the engine's per-device default never decides residency. AMD and Intel integrated GPUs, where reading on demand halved prefill, keep the engine default and are priced with the table loaded.
Qwen4Exp's QSA attention scores the whole window each microbatch. Under the router's unified KV every slot sees the full window, and b11370 reserves ~46 bytes per microbatch token per window token for each context, device plus host; an MTP head is a second context. At 256K that is 5.9 GiB per context at -ub 512 and ~23 GiB at -ub 2048, so the prefill posture Hermes chose on a 128 GB Spark loaded the target and ran out of memory creating the MTP context. plan_launch now prices each posture with its own microbatch and context count, LaunchPlan carries the profile it priced, and catalog entries name their architecture so the price holds before download. On the Spark, Flash Next now runs 256K at -ub 512, priced at 83.1 GiB against 81.5 GiB of measured engine buffers.
unsloth's MTP heads mark the MTP block as plain attention (compress ratio 0) beside a QSA trunk. Mainline llama.cpp then builds the k-pool inputs with nothing reading k_idxs, and the server aborts at load with GGML_ASSERT(buffer), on b11370 and b11430 alike. ggml-org publishes a head that loads. On a DGX Spark at 256K and temperature 1.0 it drafts at 39 tok/s on prose and 32 on code, against ~27.5 without MTP. AssetFile gains a repo so a companion can download from a repo other than the model's.
The six locale files that carry local-models copy sit past the file-size cap and may only shrink. Each settings.localModels block moves into <locale>_local_models.ts, as the model-menu copy did, so the next string lands without growing them. No string changes.
The RTX Spark ships with the 27B as its default model. Flash Next fits its 128 GB pool and stays available, but Windows backs GPU memory with commit outside the carve-out, so Flash Next loads only once the carve-out leaves Windows enough memory to back it: with no page file, an 8 GB carve-out loads it and a 64 or 96 GB one does not. recommended_entry consults a product-default table before the derived rule, keyed like the measured-rate table on platform, effective backend and recognized GPU. The derived rule still decides when the default is ineligible or cannot run resident, under a Vulkan or CPU engine, and on the DGX Spark. The Recommended badge reads "The default model for this machine, chosen by its manufacturer."
Older apps refresh catalog.json from main and ignore fields they do not know. Given this catalog, main's code recommends Flash Next on an RTX Spark and launches it without its MTP head, with the 26.8 GiB per-layer table loaded and its QSA compute buffer unpriced. Catalog schema 2 makes those apps skip the refresh and keep the catalog they shipped with; they read schema 2 once they update.
…atalog describes Downloads resolved files from main, so a re-upload on the publisher's side would change the bytes behind the catalog's exact sizes and the pricing measured from them. AssetFile carries a revision and the download plan resolves each file at it. Flash Next pins unsloth's weights and projector to 38bb39ee and ggml-org's head to 052beea; entries without a revision still follow main. Co-authored-by: Ryan Hill <38674843+RyanUnderhill@users.noreply.github.com>
ggml-org's head and unsloth's share a filename, and a download is skipped when its destination already exists. A machine holding unsloth's head, which aborts mainline llama.cpp at load, would launch with it instead of fetching ggml-org's. The head now lands as mtp-Qwen3.8-Flash-Next-ggml-org-Q8_0.gguf. Co-authored-by: Ryan Hill <38674843+RyanUnderhill@users.noreply.github.com>
Desktop Thinking Off can arrive as {effort: "none"} without enabled=False,
so V4 kept sending thinking:enabled and billed reasoning tokens.
Co-authored-by: Cursor <cursoragent@cursor.com>
…ge (NousResearch#76535) $focusedWorkspaceCwd's tile branch searched only the paginated $sessions recents page, so a restored cold tile older than that window (and outside the project preview caps) resolved an empty cwd and Files showed no project, even while the row sat in a loaded sibling store. Resolve the tile's session row through the same loaded-row space the owner lookup and tileStoredRow already use — recents + cron + messaging + unlisted owner rows (where a hidden bot chat's resolved row is parked on purpose) + the project tree's preview/lane rows — keyed by the tile's durable stored session id, never by persisting the cwd on the tile identity (cwd is backend-authoritative state another surface can change; the renderer only caches it). Unresolved secondary focus still fails closed: no rung falls through to the primary's cwd.
…tes, breaking the projects cycle The NousResearch#76535 diff grew session-states.ts past its main-pinned FILE_LINES cap (3264 > 3226) and its ./projects import pulled store/gateway into every suite that partially mocks the gateway, killing check:test:ui. Offset per AGENTS.md: the create→foreground owner-hold block moves to topical sibling session-owner-holds.ts (moved code keeps its cap), and $projectTree moves to leaf project-tree.ts so session-states reads it without the gateway edge; projects.ts re-exports it, so every existing import path is unchanged.
… on restart
recover_pending_to_db walks sorted(flush_dir.glob("*.json")) to replay
spool files left behind by a restart. Spool files are named
pending-<uuid4>.json by _write_payload, so that sort is effectively
random: a transcript recovered after a crash comes back scrambled.
The damage is permanent rather than cosmetic. SessionDB restores a
conversation with "ORDER BY id" — AUTOINCREMENT insertion order, never
timestamp — precisely so a non-monotonic clock cannot sort an assistant
tool_calls row after its tool response, "breaking tool-call/response
adjacency and triggering an HTTP 400 on replay". Replaying the spool in
filename order writes exactly that inversion into the row ids, so the
session errors out on the next turn.
The payloads already carry the ordering fields: spool_dropped_transcript_
message stamps every one with "ts" and a monotonic "seq". The sibling
consumer of the same spool in this file, drain_transcript_spool, already
sorts on them via sorted(entries, key=lambda e: e[:3]); only the restart
path ignored them. Order on (ts, seq, filename) so the live drain and the
cross-restart drain agree.
Ordering by the payload rather than by a new file-naming scheme also
recovers spool files that were written before this change, which are the
ones an affected user already has on disk.
Payloads that cannot be parsed keep their previous treatment: they sort
last, are handed back unparsed, and are re-read inside the loop so the
existing handler reports and preserves them unchanged.
(cherry picked from commit 0e46604)
(cherry picked from commit c043f01)
…pooled messages The cross-restart replay of a cap-dropped transcript message forwarded only four fields to append_message — session_id, role, content, timestamp — and then unlinked the spool file, making the loss permanent. Everything else on the message was discarded: tool_calls, tool_call_id, tool_name, the reasoning columns, codex items, platform_message_id, observed, and the api_content sidecar. append_message already accepts all of them, and the payload already carries them, because spool_dropped_transcript_message writes the full transcript message dict. Two of the dropped fields are load-bearing rather than decorative. Losing tool_calls/tool_call_id orphans a tool result from the call it answers, which is the adjacency SessionDB's "ORDER BY id" exists to protect. Losing api_content contradicts the requirement stated at the live writer, that the sidecar "must survive any gateway-side persistence path or the next turn's replay diverges at this row" — and this is such a path. The same spool is drained two ways: by drain_transcript_spool during live operation, which replays through SessionStore._append_transcript_message and keeps every field, and by this function after a restart. Same files, same payloads, different fidelity. Mirror _append_transcript_message field for field so the outcome no longer depends on whether the gateway happened to restart, including its role gate on the assistant-only reasoning columns and its message_id fallback for platform_message_id. Fields are whitelisted explicitly rather than splatted from the message dict: the payload is arbitrary JSON from disk, and an unexpected key would raise TypeError and abort the whole recovery pass. content is now passed through as-is instead of being coerced to "", since an assistant tool-call row legitimately has no content. (cherry picked from commit 99ef8fb) (cherry picked from commit 27ee00d)
…pend Ordering the spool files fixes the happy path only. If append_message fails partway through — the DB is still unhealthy, which is the situation that produced the spool in the first place — the loop logs the failure, keeps that file for a later retry, and then carries on and writes the messages that come after it. Those later messages land now; the failed one lands on some future start. Because SessionDB orders a conversation by AUTOINCREMENT id, the retried message then gets a HIGHER row id than the messages it originally preceded, and the inversion this pass just prevented is written to disk anyway — this time permanently, since both files are gone. drain_transcript_spool, the live drain of the same spool, already states the rule: "On the first replay failure the drain stops and remaining files are kept for the next attempt (the DB is likely still unhealthy)." Apply it here too. The block is per-session rather than global. Replay order is only defined within a session, and this function drains every session's spool in one pass, so a single unhealthy session must not strand the others' messages on disk. Non-transcript pending payloads are unaffected. (cherry picked from commit 5ea5104) (cherry picked from commit a959154)
Names the spool files so that filename order is the exact reverse of drop order, which is what uuid4 names produce on average, and asserts the replay comes back in drop order. A second case gives three payloads the same one-second ts so only the monotonic seq can separate them. Both fail on the previous sorted(glob()) implementation. A third case pins the pre-existing treatment of a corrupt payload — reported by the loop's own handler and left on disk — so the new ordering pass cannot silently swallow one. Lives in its own file rather than tests/gateway/test_shutdown_flush.py to keep the restart-recovery cases together with the spool fixtures they need. (cherry picked from commit 555d871) (cherry picked from commit c372b25)
…overy Round-trips an assistant tool-call row carrying every field the live writer persists and asserts each one reaches append_message: tool_calls, tool_call_id, tool_name, the reasoning and codex columns, platform_message_id, observed, timestamp, and the api_content sidecar. Also pins content=None passing through uncoerced, since an assistant tool-call row has no content, and the role gate that keeps the assistant-only reasoning columns off a user row. A separate case covers a failed replay: with the first of two messages for one session rejected, neither is written and both spool files stay on disk, while a third message for an unrelated session still recovers. All but the two behaviour-preservation cases fail on the previous implementation. (cherry picked from commit 869acc6) (cherry picked from commit e02d9fc)
… absent
_transcript_append_kwargs chose the spooled message's timestamp with
`message.get("timestamp") or payload.get("ts")`, carried over from the
code this PR replaces. Truthiness is the wrong test: epoch 0 is a valid
timestamp and would be silently rewritten to the payload's ts, which is
the fidelity loss this PR exists to remove.
Fall back only when the field is genuinely absent.
(cherry picked from commit 62206b0)
(cherry picked from commit a7b6828)
…uilder Review follow-ups (teknium1): - One kwargs builder, two callers. transcript_append_kwargs() in gateway/session_transcript.py builds the append_message kwargs, and both _append_transcript_message and the spool's restart replay call it, so the restart writer cannot lag the live writer again (display_kind and display_metadata were added after NousResearch#84785 was written). fallback_ts applies only to a message with no timestamp; the live writer passes none, so its rows are byte-identical. - A failed replay says what it held back: one INFO line per pass with the file count per blocked session, so a spool dir that never empties has a reason in the log. - seq is per process and ts has one-second resolution; the sort key now says so where it reads seq. - Tests fold to three invariants: drop order beats name order (seq breaks a same-second tie), a replayed row is the row the live writer writes, and a failed replay holds back that session's later messages only. The two that already passed on main (a corrupt file is kept; the payload-clock fallback) are dropped; the fallback is asserted inside the fidelity test. (cherry picked from commit e2bacb0)
…n sibling restorePendingApproval joins restore-pending-clarify and restore-pending-connection as a topical sibling of use-session-actions, so the resume module sheds lines before the next fix touches it (code-health file cap). No behaviour change.
Branching the open chat calls navigate(child) and then resumeSession(child)
in the same tick. resumeSession captures the route token at entry, but
the router only publishes the new pathname on the next render, so the
token it captured still named the PARENT. When that render landed while
resume was awaiting resolveStoredSession/gateway, isCurrentResume() saw
the token change and returned, abandoning the resume before it bound the
warm branch runtime. The pane was left with the child selected, no
active runtime, an empty transcript and a loader for a row with history;
use-route-resume saw nothing to heal (selection already matched the
route, creatingSessionRef had suppressed the pathname edge), so the
child stayed on the spinner with no composer until the user switched
away and back.
isCurrentResume now treats the route arriving at the session being
resumed as still current (resumeRouteStillCurrent in
session-context-drift.ts); a route to any other chat still supersedes
the resume exactly as before.
De-flakes Desktop core E2E lineage-sidebar ("a branch child is titled
and is its own row"), which hit this ~2 in 10 runs.
… (default lane) NousResearch#134320 shipped lmstudio / tencent-tokenhub profiles with empty env_vars. The provider-catalog e2e harness reads key vars from the profile only, so main's `Python tests / e2e` went red (56 KeyError: '' errors + 2 switch failures); NousResearch#134385 restored the env vars. The PR's own e2e lane had been skipped by the change classifier, which is how it merged green. This invariant runs on the default test lane, which is never skipped: every api_key profile backed by an api_key PROVIDER_REGISTRY row declares that row's key variable first in env_vars. It fails on the pre-NousResearch#134385 tree naming both profiles and passes on main.
…ting cache still egresses (cherry picked from commit fb4351a)
…gateway's adapters instead of running with none (cherry picked from commit cd1314e)
… the timing assertion (cherry picked from commit 3c52910)
…er timeout and retries after 60s (cherry picked from commit e5d54c2)
(cherry picked from commit 002d0af)
… and keep the live-adapters WHY The startup gate's refusal was a one-line walrus `if ...: return refusal`, unlike every other early return in the handler, and the comment explaining why the fire needs the live adapters (E2EE / relay-fronted platforms have no native credential) was dropped with it. No behaviour change.
…low token verify cannot outlast the forwarder The 7 s startup wait was measured from the gate, which runs after the JWT verify. A slow JWKS fetch (sync verifiers run off-loop and may hit the network) plus a full 7 s wait could exceed the dashboard forwarder's 10 s timeout: NAS then sees a 503 and retries while this handler still claims and runs the job, so it runs twice. The handler now stamps its entry time and the gate's deadline is measured from it.
…ateway starts draining A gateway that is stopped during boot never reaches _running, so a fire parked in the startup wait held its drain reservation for the full budget before refusing, and stalled the shutdown drain with it. The wait now exits on _draining / _external_drain_active with the same retryable 503 it returns on timeout.
…t_delivery The fixture hand-assembled a _TargetDelivery (a Slack origin paired with a hard-wired Telegram target, a duck-typed transport, a hand-set origin_user_id), so it asserted a shape the real prologue never produces. The targets now come from _prepare_target_delivery with a live relay (or native) adapter, so origin matching, transport resolution and origin_user_id are the production ones.
…apters resolve in the fire-startup sibling The code-health ratchet holds gateway/platforms/api_server.py (over the 2k target) at its size on main, and the startup gate's follow-ups grew it by 7 lines. The handler's runner lookup, startup gate and live-adapter snapshot now resolve in one call, live_adapters_once_started() in api_server_fire_startup, which returns (refusal, adapters). The facade ends below main (4619 vs 4621). No behaviour change.
…ay finishes starting into a drain The startup wait exited as soon as _running turned True. A restart drain keeps _running True (run_shutdown sets _draining and deliberately leaves _running), and the handler's own drain check ran before the wait, so a fire whose wait ended in a drain was claimed and run anyway. The gate now checks the drain flags on every poll before it accepts readiness.
Lead fold from the independent review (quality + thermo + 2c + efficiency lenses, all Low): the fire startup gate and APIServerAdapter._gateway_is_draining now share runner_is_draining, so a future drain flag cannot be added to one and missed by the other; the gate returns early on no runner instead of looping on an unchanging guard. The cold-adapter test closes its loop and joins its thread, the slow-verify test keeps the same ordering at a fifth of the wall-clock, and the Chronos contract doc lists the startup 503.
…ead, not timers Review (greptile, tests/AGENTS.md): the startup and drain tests changed the runner after fixed sleeps, so a loaded runner could finish startup before the handler reached the gate and the slow-verify test could see a 202. The runner now signals when the gate first reads _running, and each test changes it only then. The slow-verify case completes startup at that first read, so only a budget measured from handler entry refuses.
…into route_binding try_activate_fallback's in-place swap (client, pool, compressor, reasoning, extra_body) and restore_primary_runtime's reinstall are now agent/route_binding.py::bind_route_entry and ::reinstall_primary_runtime / ::reinstall_runtime_snapshot, so a per-turn route can reuse the same binder instead of a third copy. No behaviour change.
…own model Voice replies had to come from the session's main model, so a heavy model made every spoken answer slow (NousResearch#83894). A new auxiliary slot, voice_chat, picks the model for voice turns: - CLI/TUI voice mode, the Desktop voice conversation (prompt.submit voice_turn) and messaging voice notes mark the turn; agent/voice_turn_route.py binds the route after the prompt, row and preflight compaction were settled against the main model and undoes it before memory sync and background review, so the next typed turn and the review run on the main model. - A turn larger than the voice model's compression threshold stays on the main model with a one-time notice: switching to voice never compacts the conversation. - Usage lands in session_model_usage under task voice_chat; it never rewrites the session's recorded model/route, so resume keeps the model the user picked. - Slot appears in Desktop Settings -> Models, the dashboard, and `hermes model`; GPT-Live delegations keep using the session model.
…evel where off is refused auxiliary.voice_chat.reasoning_effort ships as "none", and it applies even when the slot is on auto (the session's model answers the spoken turn with reasoning off; typed turns keep their effort). Routes that cannot switch reasoning off get their weakest accepted level instead: Responses ladders without "none" (gpt-6-astra, gpt-6.1-sol -> low), mandatory-thinking Claude, and routes the catalog or an earlier 400 marked mandatory. A disable the voice model rejects at runtime is remembered per route for the next voice turn, and the rejection flags never leak between the voice route and the main one. "Inherit" in the dashboard/Desktop writes an explicit "" so the slot default does not come back; both resets restore the shipped default.
The overlay binds its Escape handler in a passive effect keyed on the failed state, which can flush after "Installation failed" is already painted. The test pressed Escape once right after findByText, so on a loaded runner the key landed before the listener existed and the overlay (correctly) stayed up for the whole waitFor. Proof: delaying the listener by 80 ms reproduces the CI red on the old test and passes the new one.
…earch#115104) MemoryManager forwarded turns, transcripts and tool output to external memory providers without the egress scrub, so a key echoed into tool output was archived verbatim in the provider's store. Every fan-out now goes through redact_for_egress (the chat/cron delivery scrub; holds with security.redact_secrets off, fails closed): sync_all (turn strings + messages), prefetch_all / queue_prefetch_all, on_turn_start, on_session_end, on_pre_compress (both transcripts), on_memory_write (content + metadata), on_delegation and provider handle_tool_call args. Nested dicts/lists (tool-call arguments, multimodal parts) are walked; data: URLs pass; the live transcript is never mutated. A value masked anywhere in one hand-off is masked everywhere in it, so a key the model repeats bare in prose is caught. redact.py: the JSON-field rule now also matches inside JSON-encoded strings (tool results wrap config dumps in escaped JSON), which is how the reported opaque AQ.-prefixed key slipped past. Co-authored-by: Teknium <teknium1@gmail.com>
…tor-managed ones The self-improvement review fork refused every edit/patch/write_file/remove_file on a skill without `created_by: "agent"`, so lessons about hand-written, URL-installed, foreground-created, bundled, hub, external or pinned skills were dropped with only a WARNING line (77% of refused review writes on the NousResearch#134289 reporter's install). The prompts then steered the fork to a different umbrella or a new near-duplicate skill. Ownership now gates only delete/archive: the review may improve any skill it learns from (still read-before-write, ledgered, and staged when skills.write_approval is on), while autonomous archival stays limited to curator-managed, unpinned, non-bundled/hub/external skills. The review prompts drop the protected-skill lists and say never to delete. Fixes NousResearch#134289
Windows 11 25H2 Settings records the default browser under
UrlAssociations\https\UserChoiceLatest\ProgId and stops updating the legacy
UserChoice key, which UCPD.sys also write-protects. Reading only the legacy
ProgId made real-profile browsing refuse a correctly configured Edge/Chrome
default ("not a supported Chromium browser"), with WinError 2 when the key
exists without a value, or picked a stale browser when it still held one.
Resolve the effective ProgId via AssocQueryStringW(ASSOCSTR_PROGID) first,
then fall back to UserChoiceLatest\ProgId, then the legacy UserChoice value.
Classification (channels fail closed) is unchanged.
Salvages NousResearch#106971 (Shell API lookup); the UserChoiceLatest fallback follows
NousResearch#108064 and the reports on NousResearch#107854.
Co-authored-by: Konstantin Khlopkov <konstantin.khlopkov93@gmail.com>
…ult browser Get-DefaultBrowserExe read only the legacy UserChoice ProgId, which Windows 11 25H2 Settings no longer updates, so the update hand-off fell back to the WinForms card (or a stale browser) on machines whose real default is Chrome/Edge. Read UserChoiceLatest\ProgId first, legacy UserChoice second. Fixes NousResearch#108051. Follows NousResearch#108062 / NousResearch#108064. Co-authored-by: KoNit-K <konit.block@protonmail.com> Co-authored-by: Konstantin Khlopkov <konstantin.khlopkov93@gmail.com>
૮ >ﻌ< ა ci reviewran on 42789e1 — chore: sync upstream NousResearch/hermes-agent main (pure up ❌ Job failuresCheck contributors / check-attribution · View jobJob Check contributors / check-attribution failed. Python lints / code health ratchet (blocking) · View jobJob Python lints / code health ratchet (blocking) failed. |
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.
Automated pure sync PR from upstream NousResearch/hermes-agent main.
Author: jigo-hyunjin