fix(timezone): propagate configured timezone to agent prompt and terminals#10061
Open
0xnoahzhu wants to merge 1 commit into
Open
fix(timezone): propagate configured timezone to agent prompt and terminals#100610xnoahzhu wants to merge 1 commit into
0xnoahzhu wants to merge 1 commit into
Conversation
This was referenced Apr 15, 2026
This was referenced Apr 29, 2026
|
Linking this into the #17459/#17476 direction. This remains useful adjacent work if it provides consistent user timezone resolution for the agent and terminal/runtime environments. Please keep it aligned with the chosen live-time context path:
|
c61e6e1 to
14c67a4
Compare
When a user configured a timezone (HERMES_TIMEZONE env var or `timezone:` in ~/.hermes/config.yaml), `date` and TZ-honouring runtimes inside the terminal tool reported the host's wall clock instead of the user's zone, so shell output disagreed with the user's configured timezone (often UTC vs. the user's actual zone on VPS deployments). Fixed end-to-end via centralized injection points instead of touching each backend individually: - BaseEnvironment._wrap_command injects `export TZ=<name>` once. That covers local, docker, ssh, singularity, modal, daytona, and vercel_sandbox — all flow through this method. Managed Modal uses a different exec path (BaseModalExecutionEnvironment._prepare_modal_exec), so it gets the same injection there. - local.py `_make_run_env` and `_sanitize_subprocess_env` set TZ on the subprocess env so PTY/background terminals (spawned via process_registry.spawn_local) and the init_session bootstrap that runs before the snapshot exists also agree. - code_execution_tool.py `_run_local_sandbox`: previously read HERMES_TIMEZONE directly via os.getenv, silently missing config.yaml- only setups (the same bug class). Switched to hermes_time.get_timezone() so both env-var and config.yaml paths resolve consistently. The redundant inline `TZ=...` prefix on the remote-backend code path was dropped — _wrap_command already exports TZ before the script runs. - New `_resolve_configured_tz_name()` helper in tools/environments/base.py reads via hermes_time.get_timezone(), which checks both HERMES_TIMEZONE and ~/.hermes/config.yaml. CLI entry points like `hermes chat` don't bridge config.yaml -> env var (only gateway/run.py does), so an env- var-only check would silently miss config.yaml-only setups. Scope note: this PR only touches terminal TZ propagation. An earlier revision also surfaced the timezone in the agent's system prompt; that piece was removed per review feedback in NousResearch#17459/NousResearch#17476 — live time/ timezone context belongs in the ephemeral per-turn user-message path (NousResearch#15872), not the cached system prompt prefix. Terminal TZ propagation is independently useful and tested separately from prompt-cache behavior. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
14c67a4 to
a324446
Compare
Author
|
Thanks for the review and the consolidation context in #17459/#17476. I've scoped this PR down to terminal TZ propagation only:
Rebased onto latest main and squashed to a single commit. No changes to the cached system prompt or tool schemas, so nothing here interacts with the |
This was referenced May 15, 2026
quinnmacro
added a commit
to quinnmacro/hermes-upstream-fork
that referenced
this pull request
May 30, 2026
…-turn The system prompt includes a timestamp frozen at session creation time (for prompt cache stability), but labeled "Conversation started:" — agents interpret this as the current time, leading to incorrect time-sensitive behavior (e.g. saying "good night" at 9 AM in a session started at 5 AM). Split the timestamp into two layers, both using user-message injection to preserve prompt cache: | Layer | Content | Location | Frequency | | System prompt | Session started: (timezone) | Frozen | Once per session | | User message | Current time: (timezone) | Dynamic | Every turn | Key changes: 1. agent/system_prompt.py: Rename 'Conversation started' → 'Session started', add IANA timezone name, drop minute precision for daily cache stability 2. agent/conversation_loop.py: Inject current time + timezone into _plugin_user_context on every turn (cache-safe) 3. agent/chat_completion_helpers.py: Inject current time into handle_max_iterations summary request user message 4. hermes_time.py: Add get_timezone_name() public API 5. Tests: 5 new/updated tests covering cache safety, user-message injection, and max-iterations path Rebased onto upstream/main (v2026.5.29). Code adapted to 3-module refactor (run_agent.py → agent/system_prompt.py + conversation_loop.py + chat_completion_helpers.py). Approves maintainer alignment criteria: - Volatile 'Current time' kept out of cached system prompt - User timezone in ephemeral context (IANA name) - Timezone resolution aligned with NousResearch#10061 - Tests prove cache stability while per-turn time updates - No gateway-only timestamp-prefix mechanism
quinnmacro
added a commit
to quinnmacro/hermes-upstream-fork
that referenced
this pull request
Jun 11, 2026
…-turn - Rename 'Conversation started' → 'Session started' in system prompt (unambiguous frozen label) - Add timezone display line to system prompt (IANA + UTC offset, aligned with NousResearch#10061) - Inject 'Current time:' per-turn into plugin_user_context (user message, not system prompt) - Inject current time into handle_max_iterations summary prompt - Add format_current_time_context() and get_timezone_display() helpers to hermes_time.py - 11 tests covering cache safety, time injection, timezone formatting Architecture: volatile time goes to user message (preserves prompt cache prefix), frozen session metadata stays in system prompt. Adapted to v2026.6.5 refactor: turn_context.py (new file) replaces the old conversation_loop.py injection point.
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.
What does this PR do?
When a user configured a timezone (
HERMES_TIMEZONEenv var ortimezone:keyin
~/.hermes/config.yaml), the agent didn't know about it. Two visible symptoms:server-local time. "What's today's date?" returned the server's date, not
the user's.
date(and any TZ-honouring runtime) inside the terminal tool reported theserver's wall clock, so shell output disagreed with what the agent thought
the time was.
Fixed end-to-end via two centralized injection points instead of touching each
backend individually:
run_agent.py): append aTimezone: <IANA> (UTC±HH:MM)line to the timestamp block when a timezone is configured.
BaseEnvironment._wrap_commandinjectsexport TZ=<name>once. That covers local, docker, ssh, singularity, modal,and daytona — all six flow through this method. Managed Modal uses a different
exec path (
BaseModalExecutionEnvironment._prepare_modal_exec), so it getsthe same injection there.
tools/environments/base.py): new_resolve_configured_tz_name()reads via
hermes_time.get_timezone(), which checks bothHERMES_TIMEZONEand
~/.hermes/config.yaml. CLI entry points likehermes chatdon't bridgeconfig.yaml → env var (only
gateway/run.pydoes), so an env-var-only checkwould silently miss config.yaml-only setups. This resolver covers them.
Related Issue
N/A — no existing issue. Reproduced locally with
timezone: Asia/Shanghaiin~/.hermes/config.yamlrunning on a UTC server.Fixes #
Type of Change
Changes Made
run_agent.py— emitTimezone: <IANA> (UTC±HH:MM)after the timestamp whenconfigured. Uses
hermes_time.now()so the offset is computed in the user'szone.
tools/environments/base.py— new_resolve_configured_tz_name()helper;BaseEnvironment._wrap_commandinjectsexport TZ=<name>after the snapshotsource so it overrides anything inherited.
tools/environments/modal_utils.py—BaseModalExecutionEnvironment._prepare_modal_execprependsexport TZ=<name>;after sudo wrapping (coversManagedModalEnvironment).tools/environments/local.py—_make_run_envand_sanitize_subprocess_envset
TZ=<name>on the subprocess env so PTY/background terminals (spawnedvia
process_registry.spawn_local) and theinit_sessionbootstrap thatruns before the snapshot exists also agree. Refactored to use the shared
helper.
tests/run_agent/test_run_agent.py—test_includes_timezone_when_configured,test_omits_timezone_line_without_config.tests/tools/test_base_environment.py—TestWrapCommandTimezone(4) andTestResolveConfiguredTzName(3).tests/tools/test_modal_utils_tz.py—TestPrepareModalExecTimezone(4)[new file].
tests/tools/test_local_env_blocklist.py—TestTimezonePropagation(5).How to Test
export HERMES_TIMEZONE=Asia/Shanghai, ortimezone: Asia/Shanghaito~/.hermes/config.yaml(with noHERMES_TIMEZONEenv var).hermes chat. The system prompt should includeTimezone: Asia/Shanghai (UTC+08:00)right under the timestamp.date. Output should be Asia/Shanghaiwall-clock time (CST), not the server's zone.
Checklist
Code
fix:prefix; one squashed commit)pytest tests/not re-run end-to-endDocumentation & Housekeeping
is currently undocumented anywhere on https://hermes-agent.nousresearch.com/docs
(verified via the docs search). Worth a follow-up doc PR; this PR keeps
its surface area to the bug.
cli-config.yaml.exampleif I added/changed config keys —N/A: no new config keys; the existing
timezonekey just startsworking in places where it was silently ignored.
CONTRIBUTING.mdorAGENTS.mdif I changed architectureor workflows — N/A: no architecture changes.
export TZ=<name>is POSIX; thechange works on Linux, macOS, Termux, and Git Bash on Windows since every
terminal backend already shells through bash.
N/A: no tool schema or description changes.