feat(gateway): add optional message timestamps for LLM context - #41633
Closed
WolframRavenwolf wants to merge 1 commit into
Closed
feat(gateway): add optional message timestamps for LLM context#41633WolframRavenwolf wants to merge 1 commit into
WolframRavenwolf wants to merge 1 commit into
Conversation
WolframRavenwolf
force-pushed
the
feat/gateway-message-timestamps
branch
4 times, most recently
from
June 15, 2026 22:41
a5b00a1 to
359d72d
Compare
Consolidates these related Amy fork patches: - 429830f39 feat(gateway): inject message timestamps into user messages for LLM context - 3c3d6fac0 fix: handle both ISO string and epoch float timestamps in history replay - 2874f7725 feat: human-friendly timestamp format with weekday and timezone name - 3735f4c8b fix: render gateway message timestamps once
WolframRavenwolf
force-pushed
the
feat/gateway-message-timestamps
branch
from
June 16, 2026 00:38
359d72d to
ad74056
Compare
teknium1
added a commit
that referenced
this pull request
Jun 16, 2026
Follow-up to salvaged PR #41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests.
teknium1
added a commit
that referenced
this pull request
Jun 16, 2026
Follow-up to salvaged PR #41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests.
Contributor
|
Merged via #47253 — your commit was cherry-picked onto current main with your authorship preserved in git log (rebase-merge). The timestamp metadata storage, the clean-transcript handling, and the defensive de-duplication on replay are all in. One change on top: the in-context render is now gated behind |
jhjaggars-hermes
added a commit
to jhjaggars/hermes-agent
that referenced
this pull request
Jun 17, 2026
* feat: add z-ai/glm-5.2 to OpenRouter and Nous model lists Z.ai released GLM 5.2 on 2026-06-15, available on OpenRouter: - https://openrouter.ai/z-ai/glm-5.2 GLM-5.2 is Z.ai's flagship for long-horizon tasks, shipping a 1M-token context window (up from 200K on GLM 5.1) and tool calling. Per the OpenRouter API: text-only, context_length 1048576, tools supported. No separate -fast variant exists. The 1M context length, native zai picker entry, setup wizard, and Z.ai coding-plan auth entries for glm-5.2 already landed on main. This fills the remaining gap: the two aggregator surfaces where glm-5.1 appears but glm-5.2 did not. Changes: hermes_cli/models.py - Add z-ai/glm-5.2 to the OpenRouter fallback snapshot (OPENROUTER_MODELS) and the Nous Portal curated list (_PROVIDER_MODELS["nous"]), newest flagship first. Live catalogs surface it automatically when reachable; the fallback lists matter when the manifest fetch fails. website/static/api/model-catalog.json - Regenerated via scripts/build_model_catalog.py (not hand-edited) so the manifest stays in sync with the source lists; guarded by tests/hermes_cli/test_model_catalog.py. * feat(prompt): make context-file truncation limit configurable PROBLEM: Automatic context files such as SOUL.md and AGENTS.md were capped by a hardcoded CONTEXT_FILE_MAX_CHARS value. Amy's local fork had raised that constant from 20K to 25K so a larger SOUL.md would not be silently truncated, but the hardcoded 25K value changed upstream default behavior and made the patch less generally useful. SOLUTION: Restore the upstream-compatible 20K default, add a context_file_max_chars config setting for users who intentionally keep larger identity/project-context files, keep chat-visible truncation warnings, and document the new setting. Tests cover the default, config override, explicit max_chars precedence, and the warning text. * fix(prompt): isolate truncation warnings per context Follow-up to salvaged PR NousResearch#41619: replace the module-global _truncation_warnings list with a contextvars.ContextVar so concurrent gateway-session prompt builds can't drain or clear each other's pending warnings (cross-session leak). Adds a context-isolation test. * feat(desktop): stream subagent activity into watch windows (NousResearch#47060) * feat(desktop): stream subagent replies into watch windows A desktop watch window resumes a child session lazily (no full agent) and mirrors the parent-relayed `subagent.*` events into native child-session stream events. The child's streamed reply text was never relayed, so the window sat blank while the subagent "talked". - delegate_tool: forward the child's `run_conversation` stream tokens up the progress relay as `subagent.text` (inert under CLI/TUI — their progress handlers ignore non-tool event types; only a gateway watch window mirrors it). - server: mirror `subagent.text` -> `message.delta` on the child sid only, and skip the parent emit (per-token frames are meaningless on the parent session, which shows the child via the spawn tree). Demote `subagent.start` to a one-time goal header and drop the noisy `subagent.progress` mirror — tools already mirror natively. - server: guard `_start_agent_build` so a lazy watch session spectating an in-flight child stays lazy; incidental RPCs were upgrading it to a full agent mid-stream and silently killing the mirror. * fix(desktop): keep watch-window chat clear of titlebar chrome Secondary windows (new-session scratch, subagent watch, cmd-click pop-out) hide the titlebar tool cluster + session header, so the transcript ran to the window's top edge and streamed text slid up under the OS traffic lights. - Gate the hidden chrome on `isSecondaryWindow()` everywhere (app-shell, chat header, thread list) instead of the narrower new-session flag. - Add a fixed opaque drag-strip at the top of the secondary-window transcript: content padding alone scrolls away with the text, so the strip masks anything behind it and keeps the window draggable like the main header. * fix: WSL subagent window * fix: subagent window top padding --------- Co-authored-by: Austin Pickett <pickett.austin@gmail.com> Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com> * feat(hooks): session:compress event_callback for MemPalace sync * test(hooks): cover session:compress event; drop dead import Follow-up to salvaged PR NousResearch#41624: - Remove stray urllib.parse import in run_agent.py (cherry-pick cruft, unused) - Add tests: session:compress emits with correct context, no-callback is safe, and a callback exception does not break compression * feat(xai): default to grok-build-0.1 Switch the default model for the xAI/Grok provider and the xAI web search backend from grok-4.3 to grok-build-0.1. grok-build-0.1 is already recognized by the model metadata, so no new model definition is required; grok-4.3 remains selectable. * docs(xai): update default model references to grok-build-0.1 Reflect the default-model change in the xAI Grok OAuth guide, the web search docs (EN + zh-Hans), and the web provider docstring. grok-4.3 is kept in the model tables as the previous default; the Nous/OpenRouter aggregator catalog still lists grok-4.3 and is left unchanged. * fix(inventory): keep user-defined custom providers in model dedup The NousResearch#45954 model-dedup builds `user_models` from every is_user_defined row, then strips those model IDs from every row where is_aggregator(slug) is True. But is_aggregator() returns True for *every* `custom:*` slug, and list_authenticated_providers emits named custom providers with slug `custom:<name>` and is_user_defined=True. So a user's own custom provider is treated as an aggregator and filtered against user_models — which holds exactly its own models (the row helped build that set). Every model is removed, the row drops to zero, and the provider disappears from the model picker. Guard the dedup loop to skip is_user_defined rows: a user's configured provider is never an aggregator duplicate of itself. Built-in aggregators (openrouter, etc.) are still deduped as before. Adds a regression test. * chore(release): map cyb0rgk1tty noreply email for AUTHOR_MAP Salvage follow-up for PR NousResearch#46921 — CI matches contributor authorship on the commit email, which is the GitHub noreply form. * fix(telegram): resolve replies to rich (sendRichMessage) messages Telegram does not echo a sendRichMessage's content back in reply_to_message (.text/.caption empty, .api_kwargs None), so replies to rich sends (briefings, the gateway's own rich finals) arrived with no quotable text and the [Replying to: ...] injection was skipped. Remember message_id -> text at send time in a best-effort JSON index (gateway/rich_sent_store.py), and recover it on inbound when text and caption are both empty. Best-effort and no-throw throughout: any failure degrades to prior behavior and never breaks a send or message. Salvaged from NousResearch#47375 by @x1erra. Dropped the cross-platform run.py reply-prefix rewrite (out of scope; bloated every reply on every platform) and scrubbed a docstring reference to an out-of-repo script. Kept the inbound reply_to logging enrichment used to verify the fix. * chore(release): AUTHOR_MAP entry for x1erra (Sierra) * test(telegram): rich-reply recovery via send-time index Cover NousResearch#47375 fix: record-on-rich-send + lookup-on-reply round trip, lookup miss leaving reply_to_text None, and precedence (native quote and echoed caption both win over the index fallback). * fix(model-switch): probe /v1/models for providers without api_key Section 3 of list_authenticated_providers (user-defined endpoints from the providers: config section) required an api_key before probing the endpoint's /v1/models for live model discovery. This broke local self-hosted backends (llama.cpp, Ollama, vLLM, etc.) that don't require authentication — they would only ever show the single default_model from config instead of the full model catalog. Section 4 (custom_providers list) already handled this correctly with the policy: probe when api_key is set OR when no explicit models are configured. Apply the same logic to Section 3 so local backends get full model discovery without requiring a placeholder api_key workaround. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(model-switch): cover section-3 no-auth probe; map chimpera author Salvage follow-up for PR NousResearch#29575: add regression tests for the section-3 no-api_key /v1/models probe (probes bare endpoints, skips when explicit models set) and add the contributor AUTHOR_MAP entry. * fix(skills): ignore support docs in skill discovery Support files under references/, templates/, assets/, and scripts/ are progressive-disclosure data loaded through skill_view(..., file_path=...). They should not be treated as standalone skills during discovery or collision checks. This prevents archived skill packages or support markdown files inside a real skill from shadowing active skills with the same name while still allowing top-level categories named scripts/templates/assets/references. Tests cover: - pruning nested SKILL.md files inside skill support directories - preserving support-named top-level categories - avoiding skill_view collisions from support markdown - keeping archived package SKILL.md files accessible only through file_path * fix(models): pass model.base_url to fetch_models in /model picker The /model interactive picker resolved a base_url from user credentials but never passed it to ProviderProfile.fetch_models(), causing the picker to always query the provider's hardcoded default endpoint instead of the user's custom URL (e.g. a company litellm proxy). - providers/base.py: add optional base_url parameter to fetch_models() - hermes_cli/models.py: pass resolved base_url to fetch_models() - Update all subclass overrides for signature compatibility - Add 6 regression tests covering override, fallback, and integration * test(model-picker): cover two overlapping user-defined custom providers Guards that two user-defined custom endpoints exposing an overlapping model each keep their full catalog — the dedup must never cross-filter two user-defined rows against each other. * fix(desktop): re-download Electron binary via mirror when pack fails (NousResearch#47266) (NousResearch#47276) * fix(desktop): re-download Electron binary via mirror when pack fails (NousResearch#47266) Since NousResearch#38673 pinned build.electronDist to node_modules/electron/dist, electron-builder reads the Electron binary straight from there and never downloads it during `npm run pack`. That dist tree is only produced by the electron package's postinstall (install.js) during `npm ci`. When that download is blocked or throttled (GitHub's release host is unreachable in some regions), the dist is missing and the build dies with: The specified electronDist does not exist: .../node_modules/electron/dist The existing ELECTRON_MIRROR fallback in all three desktop-build paths (scripts/install.ps1, scripts/install.sh, and `hermes desktop` in hermes_cli/main.py) re-ran `npm run pack` with ELECTRON_MIRROR set — but pack never downloads Electron anymore, so the mirror was never used and the retry re-read the same missing dist. The fallback was effectively dead. Drive the mirror through electron's own downloader instead: - Add a dist-presence check + a downloader helper (Test-ElectronDist / Restore-ElectronDist, _electron_dist_ok / _restore_electron_dist, _electron_dist_ok / _redownload_electron_dist) that wipes a partial dist + the path.txt version marker (electron's install.js short-circuits on it) and re-runs `node install.js`, optionally via a mirror. - On the first retry, repopulate a missing dist from the canonical source; on the mirror retry, re-fetch through npmmirror.com, then pack. - Gate the re-download on the dist check so an unrelated build failure (tsc/vite) doesn't trigger a pointless ~200 MB refetch, and skip the final pack when the binary still can't be fetched instead of failing the same way. * test(desktop): cover Electron dist re-download mirror fallback (NousResearch#47266) Add behavior coverage for the electronDist re-download fix: - _electron_dist_ok across linux/win32/darwin, including the partial-dist case (dir present but binary missing) that makes the pinned electronDist fail. - _redownload_electron_dist: no-op when the binary is present, bail when install.js is absent, wipe a stale dist + path.txt marker and run electron's downloader with ELECTRON_MIRROR injected, and report failure when the download still produces no binary. - `hermes desktop`: the mirror fallback now drives electron's own downloader before re-running pack, and skips the final pack entirely when the binary can't be fetched. Replaces the old mirror test that asserted the (now-fixed) dead behavior of re-running `npm run pack` with ELECTRON_MIRROR set — pack never downloads Electron under the pinned electronDist, so that retry could never help. * fix(desktop): honor pre-session model pick + restore global reasoning/speed defaults (NousResearch#47447) * fix(desktop): keep the pre-session model pick selected in the picker The composer picker derived its "current" row from `model.options ?? store`, so model.options always won. Pre-session that query returns the PROFILE DEFAULT, not the sticky composer pick — so selecting a model before a session exists left the checkmark (and the picker's "current" line) on the default, making the pick look ignored even though the pill updated. Add `currentPickerSelection()`: with a live session the gateway's model.options is authoritative; pre-session the sticky `$currentModel`/`$currentProvider` wins, falling back to options. Wire it into ModelMenuPanel and ModelPickerDialog. * feat(desktop): global reasoning/speed defaults in Settings → Model The composer picker is now sticky-UI/per-session only and never writes the profile default (NousResearch#46959), but Settings → Model had no reasoning/speed control and `agent.reasoning_effort` wasn't in the curated config surface at all (`service_tier` was buried in Advanced) — so there was nowhere to set the profile default that crons/subagents/messaging resolve from. Add capability-gated Reasoning (effort) + Fast controls beside the main model, gated by the applied model's reported capabilities (reasoning defaults on, fast off when unreported — same as the composer). They read/write `agent.reasoning_effort` and `agent.service_tier` by round-tripping the config record, matching the gateway's value semantics (service_tier "fast"/"priority"/"on" ⇒ fast). * refactor(desktop): don't open the reasoning select from its row label A <label> wrapping the Select forwarded text clicks to the trigger, opening the dropdown unexpectedly. Plain row for reasoning; Fast stays a <label> so clicking its text toggles the switch (expected for a checkbox-like control). * feat(gateway): inject stable human-readable message timestamps Consolidates these related Amy fork patches: - 429830f39 feat(gateway): inject message timestamps into user messages for LLM context - 3c3d6fac0 fix: handle both ISO string and epoch float timestamps in history replay - 2874f7725 feat: human-friendly timestamp format with weekday and timezone name - 3735f4c8b fix: render gateway message timestamps once * feat(gateway): gate message timestamps behind opt-in (default off) Follow-up to salvaged PR NousResearch#41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests. * Sync homelab/main to upstream/main (17251e8) with minimal carried patches Rebased onto current upstream/main and reapplied the minimal homelab patch set: - Dockerfile: add iproute2 + GitHub CLI (gh) from official apt repo - pyproject.toml: add langfuse optional extra - plugins/observability/langfuse/__init__.py: Responses API serialization - plugins/platforms/discord/adapter.py: role-mention invocation support - tests/gateway/test_discord_role_mentions.py: role-mention test coverage - tests/plugins/test_langfuse_plugin.py: Responses API test coverage - .github/workflows/build.yml: GHCR image publish workflow Upstream changes since last sync (9d2ec8d -> 17251e8): - 35 commits from NousResearch/hermes-agent:main - Desktop: tighten composer model picker, open new sessions in compact windows - fix(skills): guard recursive skill delete against tree-escape - fix(delegation): forward background flag in delegate_task dispatch - fix(desktop): route global remote profile REST calls, sync sessions across windows - fix(desktop): open remote-gateway artifacts via authenticated download - fix(models): keep curated-first ordering in live+curated merge - fix(memory): strip skill scaffolding for all providers - feat(skills): replace shop-app with CLI-based shop skill - And more (see upstream log) Co-authored-by: Hermes Agent <hermes-agent@users.noreply.github.com> --------- Co-authored-by: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Co-authored-by: Wolfram Ravenwolf <github.com@wolfram.ravenwolf.de> Co-authored-by: teknium <127238744+teknium1@users.noreply.github.com> Co-authored-by: brooklyn! <brooklyn.bb.nicholson@gmail.com> Co-authored-by: Austin Pickett <pickett.austin@gmail.com> Co-authored-by: Jaaneek <Jaaneek@users.noreply.github.com> Co-authored-by: cyb0rgk1tty <214562553+cyb0rgk1tty@users.noreply.github.com> Co-authored-by: Sierra (Hermes Agent) <stevenn.damatoo@gmail.com> Co-authored-by: chimpera <11052595+chimpera@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: liuhao1024 <sunsky.lau@gmail.com> Co-authored-by: xxxigm <54813621+xxxigm@users.noreply.github.com> Co-authored-by: Hermes Agent <hermes-agent@users.noreply.github.com>
alanbratu
pushed a commit
to alanbratu/hermes-agent
that referenced
this pull request
Jun 17, 2026
Follow-up to salvaged PR NousResearch#41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests.
al3xar
pushed a commit
to al3xar/hermes-agent
that referenced
this pull request
Jun 17, 2026
Follow-up to salvaged PR NousResearch#41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests.
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
Follow-up to salvaged PR NousResearch#41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests.
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
Follow-up to salvaged PR NousResearch#41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests.
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
Follow-up to salvaged PR NousResearch#41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests.
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
Follow-up to salvaged PR NousResearch#41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests.
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
Follow-up to salvaged PR NousResearch#41633: the timestamp prefix injection was unconditional. Gate the in-context render behind gateway.message_timestamps.enabled (default false) at both the live-message and history-replay sites; timestamp metadata is still captured + persisted regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry, docs, and gate tests.
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.
Summary
Adds optional, stable gateway user-message timestamps for LLM context.
When enabled with:
Hermes renders user messages in model context as human-readable timestamped lines while keeping persisted transcript content clean.
Motivation
Messaging-platform agents often need temporal context: whether a message was sent minutes ago, overnight, or during a prior travel/workflow window can materially affect the answer. The previous naive approach of prefixing message text directly can contaminate persisted transcripts and duplicate timestamps on replay.
This PR adds a safer path:
Verification
python3 -m py_compile gateway/message_timestamps.py gateway/run.py gateway/session.py hermes_state.py run_agent.py agent/agent_init.py agent/conversation_loop.py tests/gateway/test_message_timestamps.pyuv run pytest tests/gateway/test_message_timestamps.py tests/test_hermes_state.py::TestMessageStorage -q -o addopts=Result: 36 passed.