Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cli-config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,16 @@ compression:
# To pin a specific model/provider for compression summaries, use the
# auxiliary section below (auxiliary.compression.provider / model).

# =============================================================================
# Anthropic prompt caching TTL
# =============================================================================
# When prompt caching is active (Claude via OpenRouter or native Anthropic),
# Anthropic supports two TTL tiers for cached prefixes: "5m" (default) and
# "1h". Other values are ignored and "5m" is used.
#
prompt_caching:
cache_ttl: "5m" # use "1h" for long sessions with pauses between turns

# =============================================================================
# Auxiliary Models (Advanced — Experimental)
# =============================================================================
Expand Down
6 changes: 6 additions & 0 deletions hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,12 @@ def _ensure_hermes_home_managed(home: Path):

},

# Anthropic prompt caching (Claude via OpenRouter or native Anthropic API).
# cache_ttl must be "5m" or "1h" (Anthropic-supported tiers); other values are ignored.
"prompt_caching": {
"cache_ttl": "5m",
},

# AWS Bedrock provider configuration.
# Only used when model.provider is "bedrock".
"bedrock": {
Expand Down
17 changes: 15 additions & 2 deletions run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -1036,8 +1036,21 @@ def __init__(
self._use_prompt_caching, self._use_native_cache_layout = (
self._anthropic_prompt_cache_policy()
)
self._cache_ttl = "5m" # Default 5-minute TTL (1.25x write cost)

# Anthropic supports "5m" (default) and "1h" cache TTL tiers. Read from
# config.yaml under prompt_caching.cache_ttl; unknown values keep "5m".
# 1h tier costs 2x on write vs 1.25x for 5m, but amortizes across long
# sessions with >5-minute pauses between turns (#14971).
self._cache_ttl = "5m"
try:
from hermes_cli.config import load_config as _load_pc_cfg

_pc_cfg = _load_pc_cfg().get("prompt_caching", {}) or {}
_ttl = _pc_cfg.get("cache_ttl", "5m")
if _ttl in ("5m", "1h"):
self._cache_ttl = _ttl
except Exception:
pass

# Iteration budget: the LLM is only notified when it actually exhausts
# the iteration budget (api_call_count >= max_iterations). At that
# point we inject ONE message, allow one final API call, and if the
Expand Down
1 change: 1 addition & 0 deletions scripts/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
"hermes@nousresearch.com": "NousResearch",
"reginaldasr@gmail.com": "ReginaldasR",
"ntconguit@gmail.com": "0xharryriddle",
"agent@wildcat.local": "ericnicolaides",
"hermes@noushq.ai": "benbarclay",
"chinmingcock@gmail.com": "ChimingLiu",
"openclaw@sparklab.ai": "openclaw",
Expand Down
60 changes: 60 additions & 0 deletions tests/run_agent/test_run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,66 @@ def test_prompt_caching_native_anthropic(self):
assert a.api_mode == "anthropic_messages"
assert a._use_prompt_caching is True

def test_prompt_caching_cache_ttl_defaults_without_config(self):
"""cache_ttl stays 5m when prompt_caching is absent from config."""
with (
patch("run_agent.get_tool_definitions", return_value=[]),
patch("run_agent.check_toolset_requirements", return_value={}),
patch("run_agent.OpenAI"),
patch("hermes_cli.config.load_config", return_value={}),
):
a = AIAgent(
api_key="test-k...7890",
model="anthropic/claude-sonnet-4-20250514",
base_url="https://openrouter.ai/api/v1",
quiet_mode=True,
skip_context_files=True,
skip_memory=True,
)
assert a._cache_ttl == "5m"

def test_prompt_caching_cache_ttl_custom_1h(self):
"""prompt_caching.cache_ttl 1h is applied when present in config."""
with (
patch("run_agent.get_tool_definitions", return_value=[]),
patch("run_agent.check_toolset_requirements", return_value={}),
patch("run_agent.OpenAI"),
patch(
"hermes_cli.config.load_config",
return_value={"prompt_caching": {"cache_ttl": "1h"}},
),
):
a = AIAgent(
api_key="test-k...7890",
model="anthropic/claude-sonnet-4-20250514",
base_url="https://openrouter.ai/api/v1",
quiet_mode=True,
skip_context_files=True,
skip_memory=True,
)
assert a._cache_ttl == "1h"

def test_prompt_caching_cache_ttl_invalid_falls_back(self):
"""Non-Anthropic TTL values keep default 5m without raising."""
with (
patch("run_agent.get_tool_definitions", return_value=[]),
patch("run_agent.check_toolset_requirements", return_value={}),
patch("run_agent.OpenAI"),
patch(
"hermes_cli.config.load_config",
return_value={"prompt_caching": {"cache_ttl": "30m"}},
),
):
a = AIAgent(
api_key="test-k...7890",
model="anthropic/claude-sonnet-4-20250514",
base_url="https://openrouter.ai/api/v1",
quiet_mode=True,
skip_context_files=True,
skip_memory=True,
)
assert a._cache_ttl == "5m"

def test_valid_tool_names_populated(self):
"""valid_tool_names should contain names from loaded tools."""
tools = _make_tool_defs("web_search", "terminal")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,9 @@ Prompt caching is automatically enabled when:
- The provider supports `cache_control` (native Anthropic API or OpenRouter)

```yaml
# config.yaml — TTL is configurable
model:
cache_ttl: "5m" # "5m" or "1h"
# config.yaml — TTL is configurable (must be "5m" or "1h")
prompt_caching:
cache_ttl: "5m"
```

The CLI shows caching status at startup:
Expand Down
Loading