hermes - ✅(Solved) Fix Add tool-runtime time advisory context for time-sensitive tools [6 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#17474Fetched 2026-04-30 06:47:20
View on GitHub
Comments
1
Participants
1
Timeline
13
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×8labeled ×3closed ×1commented ×1

Add a shared tool-runtime time context/advisory helper for tools whose side effects depend on the user's local time: email, calendars, agent mailbox, send/message tools, wait/scheduler/autonomy/watch tools, and future proactive tools.

This is the implementation fan-out from #17459. The policy direction is: surface time/contact-window facts to the agent and tool result; do not silently suppress, delay, or rewrite tool calls in the control plane.

Root Cause

Add a shared tool-runtime time context/advisory helper for tools whose side effects depend on the user's local time: email, calendars, agent mailbox, send/message tools, wait/scheduler/autonomy/watch tools, and future proactive tools.

This is the implementation fan-out from #17459. The policy direction is: surface time/contact-window facts to the agent and tool result; do not silently suppress, delay, or rewrite tool calls in the control plane.

Fix Action

Fixed

PR fix notes

PR #15872: fix: prevent stale timestamp perception by injecting current time per-turn

Description (problem / solution / changelog)

Problem

The system prompt includes a timestamp frozen at session creation time (for prompt cache stability). However, the label "Conversation started: <time>" is ambiguous — agents interpret it as the current time, leading to incorrect time-sensitive behavior.

Observed: In a session started at 5:07 AM, the agent said "good night 🌙" at 9:00 AM because the only timestamp it saw was "Conversation started: Sunday, April 26, 2026 05:07 AM".

Solution

Split the timestamp into two layers, both using user-message injection to preserve prompt cache:

LayerContentLocationFrequency
System promptSession started: <time> (timezone)FrozenOnce per session (cache-stable)
User messageCurrent time: <time>\nTimezone: <tz>DynamicEvery turn (cache-safe)

Why user-message injection (not system prompt)?

PR #10448 addresses the same bug but injects Current time: into the system prompt via _build_turn_scoped_system_prompt(). This changes the system prompt content every turn, which breaks prompt cache prefix.

This PR injects into the user message — the same mechanism that plugins use (pre_llm_call context). The Hermes codebase itself documents this principle:

"Context is ALWAYS injected into the user message, never the system prompt. This preserves the prompt cache prefix — the system prompt stays identical across turns so cached tokens are reused."

User-message injection also composes naturally with the existing plugin system — time context merges with plugin context in _plugin_user_context.

Changes

  1. run_agent.py_build_system_prompt(): Rename "Conversation started""Session started" and add timezone name
  2. run_agent.pyrun_conversation(): Inject current time + timezone into _plugin_user_context on every turn
  3. run_agent.py_handle_max_iterations(): Inject current time into the summary request user message (recovery path)
  4. hermes_time.py: Add get_timezone_name() public API
  5. Comment update: Layer 6 comment now documents the split explicitly
  6. Tests: 4 new/updated tests covering cache safety, user-message injection, and max-iterations path

Test results

tests/run_agent/test_run_agent.py::TestBuildSystemPrompt::test_includes_datetime PASSED
tests/run_agent/test_run_agent.py::TestBuildSystemPrompt::test_excludes_current_time_from_cached_prompt PASSED
tests/run_agent/test_run_agent.py::TestHandleMaxIterations::test_summary_injects_current_time_into_user_message PASSED
tests/run_agent/test_run_agent.py::TestRunConversation::test_turn_level_time_injected_into_user_message PASSED

Relation to #10448

Same bug, different injection location. See my comment on #10448 for the full comparison. Key difference: this PR preserves prompt cache by using user-message injection; #10448 breaks cache by modifying the system prompt per turn.

Token impact

~50 tokens per turn (two lines: current time + timezone). This is minimal compared to typical tool schemas and memory blocks, and the information is essential for correct agent behavior.

Changed files

  • hermes_time.py (modified, +40/-0)
  • run_agent.py (modified, +48/-4)
  • tests/run_agent/test_run_agent.py (modified, +73/-2)
  • tests/run_agent/test_run_agent_codex_responses.py (modified, +31/-0)

PR #9784: feat(gateway): add optional inbound message timestamp prefix

Description (problem / solution / changelog)

What does this PR do?

<!-- Describe the change clearly. What problem does it solve? Why is this approach the right one? -->

This PR adds an optional configuration flag message_timestamp_prefix that prepends a compact timestamp to inbound user messages before they are passed to the agent.

When enabled, Hermes will prefix inbound messages like:

[04-14 23:47] hello

Related Issue

<!-- Link the issue this PR addresses. If no issue exists, consider creating one first. -->

Fixes #9628

Type of Change

<!-- Check the one that applies. -->
  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • [着 ] ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

<!-- List the specific changes. Include file paths for code changes. -->

Config support

Added new optional gateway config:

gateway.message_timestamp_prefix: true

Implemented:

  • config parsing support
  • nested gateway: YAML compatibility
  • serialization/deserialization handling

Files:

gateway/config.py

Runtime timestamp injection

Prepends timestamp during inbound message preprocessing:

gateway/run.py::_prepare_inbound_message_text()

Adds helper:

_format_inbound_message_timestamp()

Behavior:

  • format: [MM-DD HH:MM]
  • default timezone: UTC
  • override via:
HERMES_TIMEZONE=America/New_York

Gracefully falls back to UTC if timezone invalid.

Ordering guarantees

Preserves sender-prefix ordering in shared threads:

[timestamp] [username] message

Example:

[04-14 23:47] [alice] can you check this later?

Tests added

Added coverage for:

  • config parsing via nested gateway: dict
  • config.yaml loading bridge behavior
  • timestamp prefix formatting
  • ordering with username prefixes
  • UTC default handling

Files:

tests/gateway/test_stt_config.py

How to Test

<!-- Steps to verify this change works. For bugs: reproduction steps + proof that the fix works. -->
  1. Enable the feature in config:
gateway:
  message_timestamp_prefix: true
  1. Run Hermes normally
hermes
  1. Send a message from any platform

Expected:

[MM-DD HH:MM] your message

Optional timezone override:

export HERMES_TIMEZONE=America/New_York

Restart Hermes and verify updated timestamps.

Checklist

<!-- Complete these before requesting review. -->

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: <!-- e.g. Ubuntu 24.04, macOS 15.2, Windows 11 -->

Documentation & Housekeeping

<!-- Check all that apply. It's OK to check "N/A" if a category doesn't apply to your change. -->
  • [] I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • [] I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • [] I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

For New Skills

<!-- Only fill this out if you're adding a skill. Delete this section otherwise. -->
  • This skill is broadly useful to most users (if bundled) — see Contributing Guide
  • SKILL.md follows the standard format (frontmatter, trigger conditions, steps, pitfalls)
  • No external dependencies that aren't already available (prefer stdlib, curl, existing Hermes tools)
  • I've tested the skill end-to-end: hermes --toolsets skills -q "Use the X skill to do Y"

Screenshots / Logs

<!-- If applicable, add screenshots or log output showing the fix/feature in action. -->

Changed files

  • gateway/config.py (modified, +14/-0)
  • gateway/run.py (modified, +14/-0)
  • scripts/release.py (modified, +1/-0)
  • tests/gateway/test_stt_config.py (modified, +89/-1)

PR #5487: feat(gateway): inject current timestamp into user messages

Description (problem / solution / changelog)

Summary

Injects a human-readable timestamp at the top of every user message before it reaches the agent, so the agent always knows the current date and time without needing to invoke a tool call.

Format: [Current time: Monday, April 06, 2026 07:36 AM]

Why

Agents running long conversations often lose track of time. The only built-in mechanism is the Conversation started: header, which becomes stale after minutes or hours of back-and-forth. Currently the agent must spend a date tool call just to answer "what time is it?" or "what day is today?" — wasting tokens and latency.

This is especially important for:

  • Cron-scheduled tasks — the agent wakes up with no conversation history and no time context
  • Long multi-turn conversations — the original timestamp is hours old
  • Time-sensitive instructions — "remind me at 3pm", "is it past midnight?", etc.

What Changed

Core Injection (gateway/run.py)

Inside _run_agent(), right before agent.run_conversation():

  • Check display.gateway_timestamp config (default: true)
  • Get current time via datetime.now()
  • Set locale to en_US.UTF-8 for consistent day/month names (with graceful fallback)
  • Format as human-readable string: Monday, April 06, %Y %I:%M %p
  • Prepend [Current time: ...]\n\n to the message

Config Toggle (hermes_cli/config.py)

  • Add display.gateway_timestamp: true to DEFAULT_CONFIG
  • Persisted in config.yaml — survives restarts

Slash Command (hermes_cli/commands.py + gateway/run.py)

  • Add /timestamps (alias /ts) command — toggles injection on/off
  • Available in both CLI and messaging gateway
  • Takes effect immediately without restart
  • Persists to config.yaml

Design Decisions

DecisionRationale
datetime.now() instead of injecting into system promptThe message-level approach works for ALL model providers (some strip system prompts)
Locale en_US.UTF-8 with try/except fallbackServer locale may be non-English; we want consistent English output
Prepend to message, not modify agent APIZero-impact change — no changes to agent interface, prompt builder, or context files
Only in _run_agent (gateway path)CLI mode has direct terminal access; gateway agents need the injection
Config toggle + slash commandSome users may not want the token overhead; easy on/off without code changes
Default trueTime awareness is universally useful; users who don't want it can opt-out

Testing

Manual testing on self-hosted instance:

  • Agent correctly reports current time without any tool calls
  • Works across long multi-turn conversations (timestamp updated per message)
  • Cron jobs also receive timestamps (they pass through the same _run_agent path)
  • Gracefully handles missing en_US locale (falls back to server default)
  • /timestamps toggle works — ON/OFF states persist across messages and restarts
  • Config display.gateway_timestamp: false correctly disables injection

Changed files

  • gateway/run.py (modified, +76/-0)
  • hermes_cli/commands.py (modified, +2/-0)
  • hermes_cli/config.py (modified, +1/-0)

PR #17531: docs: document tool-time advisory-not-enforcement principle

Description (problem / solution / changelog)

Closes #17475.

Summary

Adds TOOL-PRINCIPLES.md at repo root capturing the architectural invariant established in #17459: runtime time/timezone/contact-window facts are surfaced to the agent as advisory context, not silently enforced in the control plane.

Why this doc needs to exist

Two closed-unmerged PRs tried to add quiet-hours enforcement and were rejected for the same reason:

  • #4711 feat: Add profile-scoped gateway autonomy runtime — held contacts in control plane
  • #7402 feat: drain Ockham outbox notifications via Hermes scheduler — held approvals during quiet hours

Without a durable written principle, it's easy for a future contributor to re-invent the same shape and get rejected again. The doc also names the preferred shape (#10421 / #15872: inject Current time + timezone into the ephemeral user-message context) so reviewers and contributors have a positive template to point at.

Shape

Each principle follows a consistent template so future invariants can be appended without re-designing the structure:

  1. Title — one-line statement of the invariant
  2. Decision — date + originating issue/PR references
  3. Why — failure mode the inverse produces
  4. What this means in practice — rejected vs encouraged PR shapes
  5. Related history — closed PRs demonstrating the wrong shape + open PRs doing it right

This first entry documents tool-time. Future candidates (credential-scope boundaries, cross-platform identity, etc.) can slot in identically.

Not in scope

  • No behavior change.
  • Does not close #10421, #15872, #17474 (those implement the principle; this just writes it down).
  • Does not take a position on whether opt-in quiet-hours enforcement should exist as a gateway plugin — the doc explicitly leaves that door open if visibility/overridability constraints are met.

File

  • TOOL-PRINCIPLES.md (new, 88 lines)

Changed files

  • TOOL-PRINCIPLES.md (added, +88/-0)

PR #17548: feat(tools): tool-runtime time advisory context helper

Description (problem / solution / changelog)

Closes #17474 (helper layer). Follow-up to #17459 and the principle documented in #17531 TOOL-PRINCIPLES.md §1.

Summary

Adds tools/tool_runtime_time_context.py: a shared helper that resolves the user's local time, timezone, and configured contact window, and surfaces them as structured context a time-sensitive tool (email send, calendar create, agent mailbox, gateway delivery, scheduler) can include in its return JSON.

Advisory only. Hermes core never silently blocks, delays, or rewrites tool calls based on the contact window. The agent sees the facts and decides. This is the positive shape that TOOL-PRINCIPLES.md §1 explicitly names, in contrast to the control-plane-enforcement shape in closed-unmerged #4711 / #7402.

Surface

from tools.tool_runtime_time_context import build_tool_runtime_time_context

ctx = build_tool_runtime_time_context()
result = {"sent": True, "time_context": ctx.to_dict()}

Returned dict (matches the issue's proposed shape):

{
  "now": "2026-04-29T02:14:00-07:00",
  "user_timezone": "America/Los_Angeles",
  "contact_window": ["08:00", "23:00"],
  "within_contact_window": false,
  "advisory": "It is 02:14 in the user's timezone (America/Los_Angeles). This is outside their configured contact window (08:00–23:00). Consider asking or deferring unless urgent."
}

Config

All optional, top-level in config.yaml:

tool_runtime:
  user_timezone: "America/Los_Angeles"   # IANA TZ; falls back to system local
  contact_window:
    start: "08:00"                       # HH:MM
    end:   "23:00"                       # HH:MM; end is exclusive

Defaults to 08:00–23:00 in the system local timezone when neither is configured. HERMES_USER_TIMEZONE env var overrides user_timezone. Wrap-around windows (22:00 → 06:00) supported. Malformed values fall back to defaults rather than raising — a bad config entry must never break a tool call on the hot path.

Not in scope (follow-ups)

This PR intentionally ships the helper + tests only. Per-tool integrations land as separate PRs so each can be reviewed on its own merits against its specific return shape:

  • email send tools
  • calendar create/update tools
  • agent mailbox / inbox/outbox tools
  • send_message / gateway delivery tools
  • wait / scheduler / cron / autonomy-watch tools

Follow-up checklist tracked in #17474.

Tests

11 tests under tests/tools/test_tool_runtime_time_context.py:

  • HH:MM parser (valid + every class of invalid)
  • _within_window normal hours / crosses midnight / zero-width
  • default config inside + outside window
  • configured user timezone shifts the clock correctly
  • custom contact window
  • wrap-around window at night
  • unknown TZ falls back silently to system local
  • naive datetime rejected with ValueError
  • to_dict() returns json-serializable shape (list, not tuple, for contact_window)
  • malformed window entries fall back to defaults

Files

  • tools/tool_runtime_time_context.py — new, 185 lines
  • tests/tools/test_tool_runtime_time_context.py — new, 196 lines

Changed files

  • tests/tools/test_tool_runtime_time_context.py (added, +182/-0)
  • tools/tool_runtime_time_context.py (added, +199/-0)

Code Example

ToolRuntimeTimeContext(
    now=<aware datetime>,
    user_timezone="America/Los_Angeles",
    contact_window=("08:00", "23:00"),
    within_contact_window=True | False,
    advisory="It is 02:14 in the user's timezone; this may be outside their normal contact window. Ask/consider deferring unless urgent.",
)

---

{
  "sent": false,
  "time_context": {
    "now": "2026-04-29T02:14:00-07:00",
    "user_timezone": "America/Los_Angeles",
    "contact_window": ["08:00", "23:00"],
    "within_contact_window": false,
    "advisory": "This is outside the user's normal contact window. Consider asking or deferring unless urgent."
  }
}
RAW_BUFFERClick to expand / collapse

Summary

Add a shared tool-runtime time context/advisory helper for tools whose side effects depend on the user's local time: email, calendars, agent mailbox, send/message tools, wait/scheduler/autonomy/watch tools, and future proactive tools.

This is the implementation fan-out from #17459. The policy direction is: surface time/contact-window facts to the agent and tool result; do not silently suppress, delay, or rewrite tool calls in the control plane.

Motivation

Quiet-hours enforcement in the control plane hides important runtime facts from the agent. For time-sensitive operations the agent should see:

  • current time in the user's timezone
  • configured/user timezone
  • whether the action appears inside a normal contact window
  • advisory text when it is outside that window

The agent can then decide whether to proceed, ask for confirmation, defer, or explain urgency.

Proposed shape

Add a small shared context/helper available to tool handlers, e.g.:

ToolRuntimeTimeContext(
    now=<aware datetime>,
    user_timezone="America/Los_Angeles",
    contact_window=("08:00", "23:00"),
    within_contact_window=True | False,
    advisory="It is 02:14 in the user's timezone; this may be outside their normal contact window. Ask/consider deferring unless urgent.",
)

Tools can include the advisory in returned JSON/result text when relevant. For example:

{
  "sent": false,
  "time_context": {
    "now": "2026-04-29T02:14:00-07:00",
    "user_timezone": "America/Los_Angeles",
    "contact_window": ["08:00", "23:00"],
    "within_contact_window": false,
    "advisory": "This is outside the user's normal contact window. Consider asking or deferring unless urgent."
  }
}

Initial tool surface

  • email send / mailbox tools
  • calendar tools for creating/updating events or reminders
  • agent mailbox / inbox/outbox style tools
  • send_message / gateway delivery tools where applicable
  • wait / scheduler / cron / autonomy-watch style tools

Acceptance criteria

  • Shared helper resolves aware now and configured user timezone consistently.
  • Time-sensitive tools can include structured time/advisory metadata in tool results.
  • Outside-contact-window behavior is advisory and visible to the agent by default.
  • No default hidden control-plane blocking, delaying, or suppression is introduced.
  • Tests cover inside-window and outside-window behavior.
  • Docs link back to #17459 and explain how new tools should consume the helper.

Related

Parent: #17459 Depends on/aligns with: #10421, #15872, #10061

extent analysis

TL;DR

Implement a shared ToolRuntimeTimeContext helper to provide time-sensitive tools with the user's local time context, enabling them to make informed decisions about proceeding with actions.

Guidance

  • Create a ToolRuntimeTimeContext class with attributes for now, user_timezone, contact_window, within_contact_window, and advisory to provide the necessary time context to tools.
  • Integrate this helper into time-sensitive tools, such as email, calendar, and send_message tools, to include structured time/advisory metadata in tool results.
  • Ensure that the shared helper resolves aware now and configured user timezone consistently across all tools.
  • Develop tests to cover both inside-window and outside-window behavior to guarantee correct functionality.

Example

class ToolRuntimeTimeContext:
    def __init__(self, now, user_timezone, contact_window):
        self.now = now
        self.user_timezone = user_timezone
        self.contact_window = contact_window
        self.within_contact_window = self.check_contact_window()
        self.advisory = self.generate_advisory()

    def check_contact_window(self):
        # logic to check if current time is within contact window
        pass

    def generate_advisory(self):
        # logic to generate advisory text based on contact window
        pass

Notes

The implementation should align with the policy direction of surfacing time/contact-window facts to the agent and tool result without silently suppressing, delaying, or rewriting tool calls in the control plane.

Recommendation

Apply workaround by implementing the proposed ToolRuntimeTimeContext helper to provide time-sensitive tools with the necessary time context, allowing them to make informed decisions about proceeding with actions. This approach ensures that the agent is aware of the user's local time and can decide whether to proceed, ask for confirmation, defer, or explain urgency.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Add tool-runtime time advisory context for time-sensitive tools [6 pull requests, 1 comments, 1 participants]