hermes - 💡(How to fix) Fix [i18n] Thai Translation: Developer Guide Part b - context-compression-and-caching, context-engine-plugin, contributing, creating-skills, cron-internals [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#15127Fetched 2026-04-25 06:24:27
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
labeled ×2

Error Message

try: load_dotenv(env_path) except UnicodeDecodeError: load_dotenv(env_path, encoding="latin-1")

Fix Action

Fix / Workaround

กรณีการใช้งานสำหรับ fallback_for_*: สร้าง skill ที่ทำหน้าที่เป็น workaround เมื่อ tool หลักไม่พร้อมใช้งาน ตัวอย่างเช่น skill duckduckgo-search ที่มี fallback_for_tools: [web_search] จะแสดงก็ต่อเมื่อ web search tool (ซึ่งต้องใช้ API key) ไม่ได้ถูกกำหนดค่าไว้

  1. Module-level override - _SCRIPT_TIMEOUT (สำหรับ tests/monkeypatching) ใช้เฉพาะเมื่อค่าแตกต่างจากค่าเริ่มต้นเท่านั้น
  2. Environment variable - HERMES_CRON_SCRIPT_TIMEOUT
  3. Config - cron.script_timeout_seconds ใน config.yaml (อ่านผ่าน load_config())
  4. Default - 120 seconds

Code Example

context:
  engine: "compressor"    # default — built-in lossy summarization
  engine: "lcm"           # example — plugin providing lossless context

---

┌──────────────────────────┐
  Incoming message   │   Gateway Session HygieneFires at 85% of context
  ─────────────────►    (pre-agent, rough est.)Safety net for large sessions
                     └─────────────┬────────────┘
                     ┌──────────────────────────┐
Agent ContextCompressorFires at 50% of context (default)
                        (in-loop, real tokens)Normal context management
                     └──────────────────────────┘

---

compression:
  enabled: true              # Enable/disable compression (default: true)
  threshold: 0.50            # Fraction of context window (default: 0.50 = 50%)
  target_ratio: 0.20         # How much of threshold to keep as tail (default: 0.20)
  protect_last_n: 20         # Minimum protected tail messages (default: 20)

# Summarization model/provider configured under auxiliary:
auxiliary:
  compression:
    model: null              # Override model for summaries (default: auto-detect)
    provider: auto           # Provider: "auto", "openrouter", "nous", "main", etc.
    base_url: null           # Custom OpenAI-compatible endpoint

---

context_length       = 200,000
threshold_tokens     = 200,000 × 0.50 = 100,000
tail_token_budget    = 100,000 × 0.20 = 20,000
max_summary_tokens   = min(200,000 × 0.05, 12,000) = 10,000

---

[Old tool output cleared to save context space]

---

┌─────────────────────────────────────────────────────────────┐
Message list                                               │
│                                                             │
[0..2]protect_first_n (system + first exchange)[3..N]  ← middle turns → SUMMARIZED[N..end]tail (by token budget OR protect_last_n)│                                                             │
└─────────────────────────────────────────────────────────────┘

---

## Goal
[What the user is trying to accomplish]

## Constraints & Preferences
[User preferences, coding style, constraints, important decisions]

## Progress
### Done
[Completed work — specific file paths, commands run, results]
### In Progress
[Work currently underway]
### Blocked
[Any blockers or issues encountered]

## Key Decisions
[Important technical decisions and why]

## Relevant Files
[Files read, modified, or created — with brief note on each]

## Next Steps
[What needs to happen next]

## Critical Context
[Specific values, error messages, configuration details]

---

[0] system:    "You are a helpful assistant..." (system prompt)
[1] user:      "Help me set up a FastAPI project"
[2] assistant: <tool_call> terminal: mkdir project </tool_call>
[3] tool:      "directory created"
[4] assistant: <tool_call> write_file: main.py </tool_call>
[5] tool:      "file written (2.3KB)"
    ... 30 more turns of file editing, testing, debugging ...
[38] assistant: <tool_call> terminal: pytest </tool_call>
[39] tool:      "8 passed, 2 failed\n..."  (5KB output)
[40] user:      "Fix the failing tests"
[41] assistant: <tool_call> read_file: tests/test_api.py </tool_call>
[42] tool:      "import pytest\n..."  (3KB)
[43] assistant: "I see the issue with the test fixtures..."
[44] user:      "Great, also add error handling"

---

[0] system:    "You are a helpful assistant...
               [Note: Some earlier conversation turns have been compacted...]"
[1] user:      "Help me set up a FastAPI project"
[2] assistant: "[CONTEXT COMPACTION] Earlier turns were compacted...

               ## Goal
               Set up a FastAPI project with tests and error handling

               ## Progress
               ### Done
               - Created project structure: main.py, tests/, requirements.txt
               - Implemented 5 API endpoints in main.py
               - Wrote 10 test cases in tests/test_api.py
               - 8/10 tests passing

               ### In Progress
               - Fixing 2 failing tests (test_create_user, test_delete_user)

               ## Relevant Files
               - main.pyFastAPI app with 5 endpoints
               - tests/test_api.py10 test cases
               - requirements.txt — fastapi, pytest, httpx

               ## Next Steps
               - Fix failing test fixtures
               - Add error handling"
[3] user:      "Fix the failing tests"
[4] assistant: <tool_call> read_file: tests/test_api.py </tool_call>
[5] tool:      "import pytest\n..."
[6] assistant: "I see the issue with the test fixtures..."
[7] user:      "Great, also add error handling"

---

Breakpoint 1: System prompt           (stable across all turns)
Breakpoint 2: 3rd-to-last non-system message  - Rolling window
Breakpoint 3: 2nd-to-last non-system message   - Rolling window
Breakpoint 4: Last non-system message          - Rolling window

---

# Cache marker format
marker = {"type": "ephemeral"}
# Or for 1-hour TTL:
marker = {"type": "ephemeral", "ttl": "1h"}

---

# config.yamlTTL สามารถตั้งค่าได้ (ต้องเป็น "5m" หรือ "1h")
prompt_caching:
  cache_ttl: "5m"

---

💾 Prompt caching: ENABLED (Claude via OpenRouter, 5m TTL)

---

⚠️  Context is 85% to compaction threshold (42,500/50,000 tokens)

---

# config.yaml
context:
  engine: "compressor"    # default built-in
  engine: "lcm"           # activates a plugin engine named "lcm"

---

plugins/context_engine/lcm/
├── __init__.py      # exports the ContextEngine subclass
├── plugin.yaml      # metadata (name, description, version)
└── ...              # any other modules your engine needs

---

from agent.context_engine import ContextEngine

class LCMEngine(ContextEngine):

    @property
    def name(self) -> str:
        """Short identifier, e.g. 'lcm'. Must match config.yaml value."""
        return "lcm"

    def update_from_response(self, usage: dict) -> None:
        """Called after every LLM call with the usage dict.

        Update self.last_prompt_tokens, self.last_completion_tokens,
        self.last_total_tokens from the response.
        """

    def should_compress(self, prompt_tokens: int = None) -> bool:
        """Return True if compaction should fire this turn."""

    def compress(self, messages: list, current_tokens: int = None) -> list:
        """Compact the message list and return a new (possibly shorter) list.

        The returned list must be a valid OpenAI-format message sequence.
        """

---

last_prompt_tokens: int = 0
last_completion_tokens: int = 0
last_total_tokens: int = 0
threshold_tokens: int = 0        # when compression triggers
context_length: int = 0          # model's full context window
compression_count: int = 0       # how many times compress() has run

---

def get_tool_schemas(self):
    return [{
        "name": "lcm_grep",
        "description": "Search the context knowledge graph",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query"}
            },
            "required": ["query"],
        },
    }]

def handle_tool_call(self, name, args, **kwargs):
    if name == "lcm_grep":
        results = self._search_dag(args["query"])
        return json.dumps({"results": results})
    return json.dumps({"error": f"Unknown tool: {name}"})

---

def register(ctx):
    engine = LCMEngine(context_length=200000)
    ctx.register_context_engine(engine)

---

1. Engine instantiated (plugin load or directory discovery)
2. on_session_start() — conversation begins
3. update_from_response() — after each API call
4. should_compress() — checked each turn
5. compress() — called when should_compress() returns True
6. on_session_end() — session boundary (CLI exit, /reset, gateway expiry)

---

context:
  engine: "lcm"   # must match your engine's name property

---

from agent.context_engine import ContextEngine

def test_engine_satisfies_abc():
    engine = YourEngine(context_length=200000)
    assert isinstance(engine, ContextEngine)
    assert engine.name == "your-name"

def test_compress_returns_valid_messages():
    engine = YourEngine(context_length=200000)
    msgs = [{"role": "user", "content": "hello"}]
    result = engine.compress(msgs)
    assert isinstance(result, list)
    assert all("role" in m for m in result)

---

git clone --recurse-submodules https://github.com/NousResearch/hermes-agent.git
cd hermes-agent

# Create venv with Python 3.11
uv venv venv --python 3.11
export VIRTUAL_ENV="$(pwd)/venv"

# Install with all extras (messaging, cron, CLI menus, dev tools)
uv pip install -e ".[all,dev]"
uv pip install -e "./tinker-atropos"

# Optional: browser tools
npm install

---

mkdir -p ~/.hermes/{cron,sessions,logs,memories,skills}
cp cli-config.yaml.example ~/.hermes/config.yaml
touch ~/.hermes/.env

# Add at minimum an LLM provider key:
echo 'OPENROUTER_API_KEY=sk-or-v1-your-key' >> ~/.hermes/.env

---

# Symlink for global access
mkdir -p ~/.local/bin
ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes

# Verify
hermes doctor
hermes chat -q "Hello"

---

pytest tests/ -v

---

try:
    from simple_term_menu import TerminalMenu
    menu = TerminalMenu(options)
    idx = menu.show()
except (ImportError, NotImplementedError):
    # Fallback: numbered menu
    for i, opt in enumerate(options):
        print(f"  {i+1}. {opt}")
    idx = int(input("Choice: ")) - 1

---

try:
    load_dotenv(env_path)
except UnicodeDecodeError:
    load_dotenv(env_path, encoding="latin-1")

---

import platform
if platform.system() != "Windows":
    kwargs["preexec_fn"] = os.setsid

---

fix/description        # Bug fixes
feat/description       # New features
docs/description       # Documentation
test/description       # Tests
refactor/description   # Code restructuring

---

<type>(<scope>): <description>

---

fix(cli): prevent crash in save_config_value when model is a string
feat(gateway): add WhatsApp multi-user session isolation
fix(security): prevent shell injection in sudo password piping

---

skills/
├── research/
│   └── arxiv/
│       ├── SKILL.md              # Required: main instructions
│       └── scripts/              # Optional: helper scripts
│           └── search_arxiv.py
├── productivity/
│   └── ocr-and-documents/
│       ├── SKILL.md
│       ├── scripts/
│       └── references/
└── ...

---

---
name: my-skill
description: Brief description (shown in skill search results)
version: 1.0.0
author: Your Name
license: MIT
platforms: [macos, linux]          # Optional - restrict to specific OS platforms
                                  #   Valid: macos, linux, windows
                                  #   Omit to load on all platforms (default)
metadata:
  hermes:
    tags: [Category, Subcategory, Keywords]
    related_skills: [other-skill-name]
    requires_toolsets: [web]            # Optional - only show when these toolsets are active
    requires_tools: [web_search]        # Optional - only show when these tools are available
    fallback_for_toolsets: [browser]    # Optional - hide when these toolsets are active
    fallback_for_tools: [browser_navigate]  # Optional - hide when these tools exist
    config:                              # Optional - config.yaml settings the skill needs
      - key: my.setting
        description: "What this setting controls"
        default: "sensible-default"
        prompt: "Display prompt for setup"
required_environment_variables:          # Optional - env vars the skill needs
  - name: MY_API_KEY
    prompt: "Enter your API key"
    help: "Get one at https://example.com"
    required_for: "API access"
---

# Skill Title

Brief intro.

## When to Use
Trigger conditions - when should the agent load this skill?

## Quick Reference
Table of common commands or API calls.

## Procedure
Step-by-step instructions the agent follows.

## Pitfalls
Known failure modes and how to handle them.

## Verification
How the agent confirms it worked.

---

platforms: [macos]            # macOS เท่านั้น (เช่น iMessage, Apple Reminders)
platforms: [macos, linux]     # macOS และ Linux
platforms: [windows]          # Windows เท่านั้น

---

metadata:
  hermes:
    requires_toolsets: [web]           # ซ่อนหาก web toolset ไม่ทำงาน
    requires_tools: [web_search]       # ซ่อนหาก web_search tool ไม่พร้อมใช้งาน
    fallback_for_toolsets: [browser]   # ซ่อนหาก browser toolset ทำงานอยู่
    fallback_for_tools: [browser_navigate]  # ซ่อนหาก browser_navigate มีอยู่

---

required_environment_variables:
  - name: TENOR_API_KEY
    prompt: "Tenor API key"               # แสดงเมื่อถามผู้ใช้
    help: "Get your key at https://tenor.com"  # ข้อความช่วยเหลือหรือ URL
    required_for: "GIF search functionality"   # สิ่งที่ต้องการตัวแปรนี้

---

terminal:
  env_passthrough:
    - MY_CUSTOM_VAR
    - ANOTHER_VAR

---

required_environment_variables:
  - name: TENOR_API_KEY
    prompt: Tenor API key
    help: Get a key from https://developers.google.com/tenor
    required_for: full functionality

---

metadata:
  hermes:
    config:
      - key: myplugin.path
        description: Path to the plugin data directory
        default: "~/myplugin-data"
        prompt: Plugin data directory path
      - key: myplugin.domain
        description: Domain the plugin operates on
        default: ""
        prompt: Plugin domain (e.g., AI/ML research)

---

skills:
     config:
       myplugin:
         path: ~/my-data

---

[Skill config (from ~/.hermes/config.yaml):
     myplugin.path = /home/user/my-data
   ]

---

hermes config set skills.config.myplugin.path ~/my-data

---

required_credential_files:
  - path: google_token.json
    description: Google OAuth2 token (created by setup script)
  - path: google_client_secret.json
    description: Google OAuth2 client credentials

---

To analyse the input, run:

    node ${HERMES_SKILL_DIR}/scripts/analyse.js <input>

---

Current date: !`date -u +%Y-%m-%d`
Git branch: !`git -C ${HERMES_SKILL_DIR} rev-parse --abbrev-ref HEAD`

---

# config.yaml
skills:
  inline_shell: true
  inline_shell_timeout: 10   # seconds per snippet

---

hermes chat --toolsets skills -q "Use the X skill to do Y"

---

hermes skills publish skills/my-skill --to github --repo owner/repo

---

hermes skills tap add owner/repo

---

{
  "id": "a1b2c3d4e5f6",
  "name": "Daily briefing",
  "prompt": "Summarize today's AI news and funding rounds",
  "schedule": {
    "kind": "cron",
    "expr": "0 9 * * *",
    "display": "0 9 * * *"
  },
  "skills": ["ai-funding-daily-report"],
  "deliver": "telegram:-1001234567890",
  "repeat": {
    "times": null,
    "completed": 42
  },
  "state": "scheduled",
  "enabled": true,
  "next_run_at": "2025-01-16T09:00:00Z",
  "last_run_at": "2025-01-15T09:00:00Z",
  "last_status": "ok",
  "created_at": "2025-01-01T00:00:00Z",
  "model": null,
  "provider": null,
  "script": null
}

---

tick()
  1. Acquire scheduler lock (prevents overlapping ticks)
  2. Load all jobs from jobs.json
  3. Filter to due jobs (next_run <= now AND state == "scheduled")
  4. For each due job:
     a. Set state to "running"
     b. Create fresh AIAgent session (no conversation history)
     c. Load attached skills in order (injected as user messages)
     d. Run the job prompt through the agent
     e. Deliver the response to the configured target
     f. Update run_count, compute next_run
     g. If repeat count exhausted → state = "completed"
     h. Otherwise → state = "scheduled"
  5. Write updated jobs back to jobs.json
  6. Release scheduler lock

---

Create a daily funding report → attach "ai-funding-daily-report" skill

---

# ~/.hermes/scripts/check_competitors.py
import requests, json
# Fetch competitor release notes, diff against last run
# Print summary to stdout - agent analyzes and reports

---

hermes cron list                    # Show all jobs
hermes cron create                  # Interactive job creation (alias: add)
hermes cron edit <job_id>           # Edit job configuration
hermes cron pause <job_id>          # Pause a running job
hermes cron resume <job_id>         # Resume a paused job
hermes cron run <job_id>            # Trigger immediate execution
hermes cron remove <job_id>         # Delete a job
RAW_BUFFERClick to expand / collapse

📄 developer-guide/context-compression-and-caching.md

Context Compression and Caching

Hermes Agent ใช้ระบบการบีบอัดแบบคู่ (dual compression system) และการแคช prompt ของ Anthropic เพื่อจัดการการใช้ context window อย่างมีประสิทธิภาพตลอดการสนทนาที่ยาวนาน

Source files: agent/context_engine.py (ABC), agent/context_compressor.py (default engine), agent/prompt_caching.py, gateway/run.py (session hygiene), run_agent.py (search for _compress_context)

Pluggable Context Engine

การจัดการ Context ถูกสร้างขึ้นบน ContextEngine ABC (agent/context_engine.py) โดย ContextCompressor ที่ติดตั้งมาให้เป็น implementation เริ่มต้น แต่สามารถใช้ปลั๊กอินอื่นมาแทนที่ได้ (เช่น Lossless Context Management)

context:
  engine: "compressor"    # default — built-in lossy summarization
  engine: "lcm"           # example — plugin providing lossless context

Engine นี้มีหน้าที่รับผิดชอบในส่วนต่างๆ ดังนี้:

  • การตัดสินใจว่าเมื่อใดที่ควรทำการบีบอัด (should_compress())
  • การดำเนินการบีบอัด (compress())
  • การเปิดเผยเครื่องมือ (tools) ที่ Agent สามารถเรียกใช้ได้ตามความจำเป็น (เช่น lcm_grep)
  • การติดตามการใช้ token จาก API responses

การเลือกใช้ Engine ถูกกำหนดผ่าน context.engine ใน config.yaml ลำดับการตรวจสอบมีดังนี้:

  1. ตรวจสอบไดเรกทอรี plugins/context_engine/<name>/
  2. ตรวจสอบระบบปลั๊กอินทั่วไป (register_context_engine())
  3. ย้อนกลับไปใช้ ContextCompressor ที่ติดตั้งมาให้

Plugin engines จะไม่ถูกเปิดใช้งานโดยอัตโนมัติ - ผู้ใช้ต้องตั้งค่า context.engine ให้เป็นชื่อของ plugin อย่างชัดเจน ค่าเริ่มต้น "compressor" จะใช้ตัวที่ติดตั้งมาให้เสมอ

สามารถตั้งค่าได้ผ่าน hermes plugins → Provider Plugins → Context Engine หรือแก้ไข config.yaml โดยตรง

หากต้องการสร้าง plugin context engine ให้ดูที่ Context Engine Plugins

Dual Compression System

Hermes มีชั้นการบีบอัดที่แยกจากกันสองชั้น ซึ่งทำงานอย่างอิสระ:

                     ┌──────────────────────────┐
  Incoming message   │   Gateway Session Hygiene │  Fires at 85% of context
  ─────────────────► │   (pre-agent, rough est.) │  Safety net for large sessions
                     └─────────────┬────────────┘
                     ┌──────────────────────────┐
                     │   Agent ContextCompressor │  Fires at 50% of context (default)
                     │   (in-loop, real tokens)  │  Normal context management
                     └──────────────────────────┘

1. Gateway Session Hygiene (85% threshold)

อยู่ใน gateway/run.py (ค้นหา Session hygiene: auto-compress) นี่คือ safety net ที่จะทำงาน ก่อนที่ Agent จะประมวลผลข้อความ เป็นการป้องกัน API failures เมื่อ session มีขนาดใหญ่เกินไประหว่าง turn ต่างๆ (เช่น การสะสมข้อมูลข้ามคืนใน Telegram/Discord)

  • Threshold: กำหนดไว้ที่ 85% ของความยาว context ของ model
  • Token source: ให้ความสำคัญกับ token ที่รายงานโดย API จริงจาก turn ล่าสุด; หากไม่สามารถทำได้จะย้อนกลับไปใช้การประมาณค่าแบบ rough character-based estimate (estimate_messages_tokens_rough)
  • Fires: ทำงานเฉพาะเมื่อ len(history) >= 4 และเปิดใช้งานการบีบอัด
  • Purpose: ดักจับ session ที่หลุดรอดจาก compressor ของ Agent เอง

Threshold ของ gateway hygiene ถูกตั้งให้สูงกว่า compressor ของ Agent โดยเจตนา การตั้งค่าไว้ที่ 50% (เท่ากับ Agent) ทำให้เกิดการบีบอัดก่อนเวลาอันควรในทุก turn ใน session gateway ที่ยาวนาน

2. Agent ContextCompressor (50% threshold, configurable)

อยู่ใน agent/context_compressor.py นี่คือ ระบบการบีบอัดหลัก ที่ทำงานภายใน tool loop ของ Agent โดยเข้าถึงจำนวน token ที่แม่นยำ ซึ่งรายงานโดย API

Configuration

การตั้งค่าการบีบอัดทั้งหมดจะถูกอ่านจาก config.yaml ภายใต้คีย์ compression:

compression:
  enabled: true              # Enable/disable compression (default: true)
  threshold: 0.50            # Fraction of context window (default: 0.50 = 50%)
  target_ratio: 0.20         # How much of threshold to keep as tail (default: 0.20)
  protect_last_n: 20         # Minimum protected tail messages (default: 20)

# Summarization model/provider configured under auxiliary:
auxiliary:
  compression:
    model: null              # Override model for summaries (default: auto-detect)
    provider: auto           # Provider: "auto", "openrouter", "nous", "main", etc.
    base_url: null           # Custom OpenAI-compatible endpoint

Parameter Details

ParameterDefaultRangeDescription
threshold0.500.0-1.0Compression triggers when prompt tokens ≥ threshold × context_length
target_ratio0.200.10-0.80Controls tail protection token budget: threshold_tokens × target_ratio
protect_last_n20≥1Minimum number of recent messages always preserved
protect_first_n3(hardcoded)System prompt + first exchange always preserved

Computed Values (for a 200K context model at defaults)

context_length       = 200,000
threshold_tokens     = 200,000 × 0.50 = 100,000
tail_token_budget    = 100,000 × 0.20 = 20,000
max_summary_tokens   = min(200,000 × 0.05, 12,000) = 10,000

Compression Algorithm

เมธอด ContextCompressor.compress() ทำงานตาม algorithm 4 เฟส:

Phase 1: Prune Old Tool Results (cheap, no LLM call)

ผลลัพธ์ของ tool เก่า (>200 chars) ที่อยู่นอก protected tail จะถูกแทนที่ด้วย:

[Old tool output cleared to save context space]

นี่คือ pre-pass ที่ประหยัดค่าใช้จ่าย ซึ่งช่วยประหยัด token จำนวนมากจาก tool outputs ที่มีรายละเอียดมาก (เช่น file contents, terminal output, search results)

Phase 2: Determine Boundaries

┌─────────────────────────────────────────────────────────────┐
│  Message list                                               │
│                                                             │
│  [0..2]  ← protect_first_n (system + first exchange)        │
│  [3..N]  ← middle turns → SUMMARIZED                        │
│  [N..end] ← tail (by token budget OR protect_last_n)        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

การปกป้อง tail เป็นแบบ token-budget based: จะเดินย้อนกลับจากส่วนท้าย (end) และสะสม token จนกว่าจะใช้ budget หมด หาก budget นั้นปกป้องจำนวนข้อความน้อยกว่าที่กำหนด จะย้อนกลับไปใช้ค่า protect_last_n ที่กำหนดไว้

Boundaries ถูกจัดเรียงให้หลีกเลี่ยงการแบ่งกลุ่ม tool_call/tool_result เมธอด _align_boundary_backward() จะเดินผ่าน tool results ที่ต่อเนื่องกัน เพื่อค้นหาข้อความ assistant ตัวแม่ (parent assistant message) โดยคงกลุ่มไว้ครบถ้วน

Phase 3: Generate Structured Summary

:::warning Summary model context length Summary model ต้องมี context window ที่ ใหญ่กว่าหรือเท่ากับ main agent model. ส่วนกลางทั้งหมดจะถูกส่งไปยัง summary model ในการเรียก call_llm(task="compression") เพียงครั้งเดียว หาก context ของ summary model มีขนาดเล็กกว่า API จะส่งข้อผิดพลาด context-length กลับมา — _generate_summary() จะดักจับข้อผิดพลาดนี้, บันทึก warning, และส่งค่า None กลับมา Compressor จะทำการลบ middle turns โดยไม่มี summary ทำให้ context การสนทนาสูญหายไปอย่างเงียบๆ นี่คือสาเหตุที่พบบ่อยที่สุดที่ทำให้คุณภาพการบีบอัดลดลง :::

Middle turns จะถูกสรุปโดยใช้ auxiliary LLM ด้วย structured template:

## Goal
[What the user is trying to accomplish]

## Constraints & Preferences
[User preferences, coding style, constraints, important decisions]

## Progress
### Done
[Completed work — specific file paths, commands run, results]
### In Progress
[Work currently underway]
### Blocked
[Any blockers or issues encountered]

## Key Decisions
[Important technical decisions and why]

## Relevant Files
[Files read, modified, or created — with brief note on each]

## Next Steps
[What needs to happen next]

## Critical Context
[Specific values, error messages, configuration details]

Summary budget จะปรับขนาดตามปริมาณ content ที่กำลังถูกบีบอัด:

  • Formula: content_tokens × 0.20 (ค่าคงที่ _SUMMARY_RATIO)
  • Minimum: 2,000 tokens
  • Maximum: min(context_length × 0.05, 12,000) tokens

Phase 4: Assemble Compressed Messages

รายการข้อความที่ถูกบีบอัดประกอบด้วย:

  1. Head messages (พร้อมหมายเหตุที่เพิ่มเข้าไปใน system prompt ในการบีบอัดครั้งแรก)
  2. Summary message (เลือก role เพื่อหลีกเลี่ยงการละเมิด role เดียวกันติดต่อกัน)
  3. Tail messages (ไม่ถูกแก้ไข)

คู่ tool_call/tool_result ที่หลงเหลือ (Orphaned) จะถูกทำความสะอาดโดย _sanitize_tool_pairs():

  • Tool results ที่อ้างถึง calls ที่ถูกลบ → ถูกลบออก
  • Tool calls ที่ผลลัพธ์ถูกลบ → ถูกแทรก stub result เข้าไป

Iterative Re-compression

ในการบีบอัดครั้งถัดไป จะมีการส่ง summary ก่อนหน้าไปยัง LLM พร้อมคำแนะนำให้ อัปเดต แทนการสรุปใหม่ทั้งหมด วิธีนี้ช่วยรักษาข้อมูลข้ามการบีบอัดหลายครั้ง — รายการจะย้ายจาก "In Progress" ไป "Done", มี progress ใหม่ถูกเพิ่มเข้ามา, และข้อมูลที่ล้าสมัยจะถูกลบออก

ฟิลด์ _previous_summary บน compressor instance จะเก็บข้อความ summary ล่าสุดไว้เพื่อวัตถุประสงค์นี้

Before/After Example

Before Compression (45 messages, ~95K tokens)

[0] system:    "You are a helpful assistant..." (system prompt)
[1] user:      "Help me set up a FastAPI project"
[2] assistant: <tool_call> terminal: mkdir project </tool_call>
[3] tool:      "directory created"
[4] assistant: <tool_call> write_file: main.py </tool_call>
[5] tool:      "file written (2.3KB)"
    ... 30 more turns of file editing, testing, debugging ...
[38] assistant: <tool_call> terminal: pytest </tool_call>
[39] tool:      "8 passed, 2 failed\n..."  (5KB output)
[40] user:      "Fix the failing tests"
[41] assistant: <tool_call> read_file: tests/test_api.py </tool_call>
[42] tool:      "import pytest\n..."  (3KB)
[43] assistant: "I see the issue with the test fixtures..."
[44] user:      "Great, also add error handling"

After Compression (25 messages, ~45K tokens)

[0] system:    "You are a helpful assistant...
               [Note: Some earlier conversation turns have been compacted...]"
[1] user:      "Help me set up a FastAPI project"
[2] assistant: "[CONTEXT COMPACTION] Earlier turns were compacted...

               ## Goal
               Set up a FastAPI project with tests and error handling

               ## Progress
               ### Done
               - Created project structure: main.py, tests/, requirements.txt
               - Implemented 5 API endpoints in main.py
               - Wrote 10 test cases in tests/test_api.py
               - 8/10 tests passing

               ### In Progress
               - Fixing 2 failing tests (test_create_user, test_delete_user)

               ## Relevant Files
               - main.py — FastAPI app with 5 endpoints
               - tests/test_api.py — 10 test cases
               - requirements.txt — fastapi, pytest, httpx

               ## Next Steps
               - Fix failing test fixtures
               - Add error handling"
[3] user:      "Fix the failing tests"
[4] assistant: <tool_call> read_file: tests/test_api.py </tool_call>
[5] tool:      "import pytest\n..."
[6] assistant: "I see the issue with the test fixtures..."
[7] user:      "Great, also add error handling"

Prompt Caching (Anthropic)

Source: agent/prompt_caching.py

ลดต้นทุน token input ลงประมาณ 75% ในการสนทนาหลาย turn โดยการแคช prefix ของการสนทนา ใช้ cache_control breakpoints ของ Anthropic

Strategy: system_and_3

Anthropic อนุญาตให้มี cache_control breakpoints ได้สูงสุด 4 จุดต่อ request Hermes ใช้กลยุทธ์ "system_and_3":

Breakpoint 1: System prompt           (stable across all turns)
Breakpoint 2: 3rd-to-last non-system message  - Rolling window
Breakpoint 3: 2nd-to-last non-system message   - Rolling window
Breakpoint 4: Last non-system message          - Rolling window

How It Works

apply_anthropic_cache_control() จะ deep-copy messages และแทรก marker cache_control:

# Cache marker format
marker = {"type": "ephemeral"}
# Or for 1-hour TTL:
marker = {"type": "ephemeral", "ttl": "1h"}

การใช้ marker จะแตกต่างกันไปตาม content type:

Content TypeWhere Marker Goes
String contentถูกแปลงเป็น [{"type": "text", "text": ..., "cache_control": ...}]
List contentถูกเพิ่มเข้าไปใน dict ของ element ล่าสุด
None/emptyถูกเพิ่มเป็น msg["cache_control"]
Tool messagesถูกเพิ่มเป็น msg["cache_control"] (สำหรับ Anthropic เท่านั้น)

Cache-Aware Design Patterns

  1. Stable system prompt: system prompt คือ breakpoint 1 และถูกแคชตลอดทุก turn หลีกเลี่ยงการแก้ไขมันกลางการสนทนา (compression จะเพิ่ม note เฉพาะในการบีบอัดครั้งแรก)

  2. Message ordering matters: Cache hits ต้องการการจับคู่ prefix การเพิ่มหรือลบ messages ตรงกลางจะทำให้ cache สำหรับทุกอย่างที่ตามมาเป็นโมฆะ

  3. Compression cache interaction: หลังจากการบีบอัด cache จะเป็นโมฆะสำหรับ region ที่ถูกบีบอัด แต่ cache ของ system prompt ยังคงอยู่ หน้าต่าง 3 messages แบบ rolling จะสร้างการแคชขึ้นมาใหม่ภายใน 1-2 turns

  4. TTL selection: ค่าเริ่มต้นคือ 5m (5 นาที) ควรใช้ 1h สำหรับ session ที่ยาวนานซึ่งผู้ใช้มีการพักระหว่าง turn

Enabling Prompt Caching

Prompt caching จะเปิดใช้งานโดยอัตโนมัติเมื่อ:

  • Model เป็น Anthropic Claude model (ตรวจพบจากชื่อ model)
  • Provider รองรับ cache_control (native Anthropic API หรือ OpenRouter)
# config.yaml — TTL สามารถตั้งค่าได้ (ต้องเป็น "5m" หรือ "1h")
prompt_caching:
  cache_ttl: "5m"

CLI จะแสดงสถานะการแคชเมื่อเริ่มต้น:

💾 Prompt caching: ENABLED (Claude via OpenRouter, 5m TTL)

Context Pressure Warnings

Agent จะปล่อย context pressure warnings เมื่อถึง 85% ของ threshold การบีบอัด (ไม่ใช่ 85% ของ context — แต่เป็น 85% ของ threshold ซึ่งตัวมันเองคือ 50% ของ context):

⚠️  Context is 85% to compaction threshold (42,500/50,000 tokens)

หลังจากการบีบอัด หากการใช้งานลดลงต่ำกว่า 85% ของ threshold สถานะ warning จะถูกเคลียร์ หากการบีบอัดไม่สามารถลดให้ต่ำกว่าระดับ warning ได้ (เพราะการสนทนามีความหนาแน่นเกินไป) warning จะยังคงอยู่ แต่การบีบอัดจะไม่ทำงานจนกว่าจะเกิน threshold อีกครั้ง


📄 developer-guide/context-engine-plugin.md


sidebar_position: 9 title: "Context Engine Plugins" description: "How to build a context engine plugin that replaces the built-in ContextCompressor"

การสร้าง Context Engine Plugin

Context engine plugins ใช้แทนที่ ContextCompressor ที่มีมาให้ในตัว ด้วยกลยุทธ์ทางเลือกสำหรับการจัดการบริบทการสนทนา ตัวอย่างเช่น engine สำหรับ Lossless Context Management (LCM) ที่สร้าง knowledge DAG แทนการสรุปแบบสูญเสียข้อมูล (lossy summarization).

วิธีการทำงาน

การจัดการบริบทของ agent ถูกสร้างขึ้นบน ContextEngine ABC (agent/context_engine.py) ContextCompressor ที่มีมาให้ในตัวคือการใช้งานค่าเริ่มต้น (default implementation) Plugin engines ต้องใช้งาน interface เดียวกัน

สามารถมี context engine ที่ทำงานได้เพียง หนึ่ง ตัวเท่านั้น การเลือกขึ้นอยู่กับการกำหนดค่า (config-driven):

# config.yaml
context:
  engine: "compressor"    # default built-in
  engine: "lcm"           # activates a plugin engine named "lcm"

Plugin engines จะ ไม่ถูกเปิดใช้งานโดยอัตโนมัติ - ผู้ใช้ต้องกำหนดค่า context.engine ให้เป็นชื่อของ plugin อย่างชัดเจน

โครงสร้าง Directory

Context engine แต่ละตัวจะอยู่ใน plugins/context_engine/<name>/:

plugins/context_engine/lcm/
├── __init__.py      # exports the ContextEngine subclass
├── plugin.yaml      # metadata (name, description, version)
└── ...              # any other modules your engine needs

ContextEngine ABC

Engine ของคุณต้องใช้งาน method ที่จำเป็น เหล่านี้:

from agent.context_engine import ContextEngine

class LCMEngine(ContextEngine):

    @property
    def name(self) -> str:
        """Short identifier, e.g. 'lcm'. Must match config.yaml value."""
        return "lcm"

    def update_from_response(self, usage: dict) -> None:
        """Called after every LLM call with the usage dict.

        Update self.last_prompt_tokens, self.last_completion_tokens,
        self.last_total_tokens from the response.
        """

    def should_compress(self, prompt_tokens: int = None) -> bool:
        """Return True if compaction should fire this turn."""

    def compress(self, messages: list, current_tokens: int = None) -> list:
        """Compact the message list and return a new (possibly shorter) list.

        The returned list must be a valid OpenAI-format message sequence.
        """

คุณสมบัติคลาส (Class attributes) ที่ engine ของคุณต้องดูแลรักษา

agent จะอ่านค่าเหล่านี้โดยตรงสำหรับการแสดงผลและการบันทึก log:

last_prompt_tokens: int = 0
last_completion_tokens: int = 0
last_total_tokens: int = 0
threshold_tokens: int = 0        # when compression triggers
context_length: int = 0          # model's full context window
compression_count: int = 0       # how many times compress() has run

method ทางเลือก

method เหล่านี้มีค่าเริ่มต้นที่เหมาะสมใน ABC สามารถเขียนทับได้ตามความจำเป็น:

MethodDefaultOverride when
on_session_start(session_id, **kwargs)No-opคุณจำเป็นต้องโหลดสถานะที่ถูกบันทึกไว้ (DAG, DB)
on_session_end(session_id, messages)No-opคุณจำเป็นต้อง flush state, close connections
on_session_reset()Resets token countersคุณมีสถานะเฉพาะ session ที่ต้องล้าง
update_model(model, context_length, ...)Updates context_length + thresholdคุณจำเป็นต้องคำนวณงบประมาณใหม่เมื่อเปลี่ยน model
get_tool_schemas()Returns []engine ของคุณมี tools ที่ agent เรียกใช้ได้ (เช่น lcm_grep)
handle_tool_call(name, args, **kwargs)Returns error JSONคุณใช้งาน tool handlers
should_compress_preflight(messages)Returns Falseคุณสามารถทำ estimate ก่อนเรียก API ที่มีค่าใช้จ่ายต่ำได้
get_status()Standard token/threshold dictคุณมี metrics ที่กำหนดเองที่ต้องการเปิดเผย

Engine tools

Context engines สามารถเปิดเผย tools ที่ agent เรียกใช้ได้โดยตรง ให้ return schemas จาก get_tool_schemas() และจัดการการเรียกใช้ใน handle_tool_call():

def get_tool_schemas(self):
    return [{
        "name": "lcm_grep",
        "description": "Search the context knowledge graph",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query"}
            },
            "required": ["query"],
        },
    }]

def handle_tool_call(self, name, args, **kwargs):
    if name == "lcm_grep":
        results = self._search_dag(args["query"])
        return json.dumps({"results": results})
    return json.dumps({"error": f"Unknown tool: {name}"})

Engine tools จะถูกแทรกเข้าไปในรายการ tools ของ agent เมื่อเริ่มต้น และถูกส่งออกไปโดยอัตโนมัติ - ไม่จำเป็นต้องลงทะเบียนใน registry

การลงทะเบียน (Registration)

ผ่าน directory (แนะนำ)

วาง engine ของคุณใน plugins/context_engine/<name>/ ไฟล์ __init__.py ต้อง export ContextEngine subclass ระบบ discovery จะค้นหาและสร้าง instance ให้โดยอัตโนมัติ

ผ่าน general plugin system

general plugin สามารถลงทะเบียน context engine ได้เช่นกัน:

def register(ctx):
    engine = LCMEngine(context_length=200000)
    ctx.register_context_engine(engine)

สามารถลงทะเบียน engine ได้เพียงตัวเดียว หาก plugin ตัวที่สองพยายามลงทะเบียน จะถูกปฏิเสธพร้อมคำเตือน

วงจรชีวิต (Lifecycle)

1. Engine instantiated (plugin load or directory discovery)
2. on_session_start() — conversation begins
3. update_from_response() — after each API call
4. should_compress() — checked each turn
5. compress() — called when should_compress() returns True
6. on_session_end() — session boundary (CLI exit, /reset, gateway expiry)

on_session_reset() ถูกเรียกใช้เมื่อใช้ /new หรือ /reset เพื่อล้างสถานะเฉพาะ session โดยไม่จำเป็นต้องปิดระบบทั้งหมด

การกำหนดค่า (Configuration)

ผู้ใช้สามารถเลือก engine ของคุณผ่าน hermes plugins → Provider Plugins → Context Engine หรือโดยการแก้ไข config.yaml:

context:
  engine: "lcm"   # must match your engine's name property

บล็อก config compression (compression.threshold, compression.protect_last_n, ฯลฯ) เป็นของ ContextCompressor ที่มีมาให้ในตัวโดยเฉพาะ Engine ของคุณควรกำหนดรูปแบบ config ของตัวเองหากจำเป็น โดยอ่านค่าจาก config.yaml ในระหว่างการเริ่มต้น (initialization)

การทดสอบ (Testing)

from agent.context_engine import ContextEngine

def test_engine_satisfies_abc():
    engine = YourEngine(context_length=200000)
    assert isinstance(engine, ContextEngine)
    assert engine.name == "your-name"

def test_compress_returns_valid_messages():
    engine = YourEngine(context_length=200000)
    msgs = [{"role": "user", "content": "hello"}]
    result = engine.compress(msgs)
    assert isinstance(result, list)
    assert all("role" in m for m in result)

ดูที่ tests/agent/test_context_engine.py สำหรับชุดการทดสอบสัญญา ABC ฉบับเต็ม

ดูเพิ่มเติม (See also)

  • Context Compression and Caching - วิธีการทำงานของ compressor ที่มีมาให้ในตัว
  • Memory Provider Plugins - ระบบ plugin แบบเลือกตัวเดียวที่คล้ายกันสำหรับ memory
  • Plugins - ภาพรวมของระบบ plugin ทั่วไป

📄 developer-guide/contributing.md


sidebar_position: 4 title: "Contributing" description: "How to contribute to Hermes Agent - dev setup, code style, PR process"

การมีส่วนร่วม (Contributing)

ขอขอบคุณสำหรับการมีส่วนร่วมใน Hermes Agent! คู่มือนี้ครอบคลุมตั้งแต่การตั้งค่าสภาพแวดล้อมสำหรับนักพัฒนา การทำความเข้าใจ codebase และขั้นตอนการรวม PR ของคุณ

ลำดับความสำคัญของการมีส่วนร่วม (Contribution Priorities)

เราให้ความสำคัญกับการมีส่วนร่วมตามลำดับนี้:

  1. การแก้ไขบั๊ก (Bug fixes) - เช่น การล่ม (crashes), พฤติกรรมที่ไม่ถูกต้อง, การสูญหายของข้อมูล
  2. ความเข้ากันได้ข้ามแพลตฟอร์ม (Cross-platform compatibility) - เช่น macOS, Linux distros ต่างๆ, WSL2
  3. การเสริมความแข็งแกร่งด้านความปลอดภัย (Security hardening) - เช่น shell injection, prompt injection, path traversal
  4. ประสิทธิภาพและความทนทาน (Performance and robustness) - เช่น retry logic, error handling, graceful degradation
  5. ทักษะใหม่ (New skills) - ที่มีประโยชน์ในวงกว้าง (ดูที่ Creating Skills)
  6. เครื่องมือใหม่ (New tools) - ที่ไม่ค่อยจำเป็น; ความสามารถส่วนใหญ่ควรอยู่ในรูปแบบของ skills
  7. เอกสาร (Documentation) - การแก้ไข, การชี้แจง, ตัวอย่างใหม่

เส้นทางในการมีส่วนร่วมทั่วไป (Common contribution paths)

  • ต้องการสร้างเครื่องมือใหม่ใช่หรือไม่? เริ่มต้นที่ Adding Tools
  • ต้องการสร้าง skill ใหม่ใช่หรือไม่? เริ่มต้นที่ Creating Skills
  • ต้องการสร้าง inference provider ใหม่ใช่หรือไม่? เริ่มต้นที่ Adding Providers

การตั้งค่าสำหรับนักพัฒนา (Development Setup)

สิ่งที่ต้องมี (Prerequisites)

RequirementNotes
Gitต้องรองรับ --recurse-submodules และติดตั้งส่วนขยาย git-lfs
Python 3.11+uv จะติดตั้งให้หากขาดไป
uvตัวจัดการ package Python ที่รวดเร็ว (install)
Node.js 20+ทางเลือก - จำเป็นสำหรับ browser tools และ WhatsApp bridge (ต้องตรงกับ engines ใน root package.json)

Clone และ ติดตั้ง (Clone and Install)

git clone --recurse-submodules https://github.com/NousResearch/hermes-agent.git
cd hermes-agent

# Create venv with Python 3.11
uv venv venv --python 3.11
export VIRTUAL_ENV="$(pwd)/venv"

# Install with all extras (messaging, cron, CLI menus, dev tools)
uv pip install -e ".[all,dev]"
uv pip install -e "./tinker-atropos"

# Optional: browser tools
npm install

กำหนดค่าสำหรับการพัฒนา (Configure for Development)

mkdir -p ~/.hermes/{cron,sessions,logs,memories,skills}
cp cli-config.yaml.example ~/.hermes/config.yaml
touch ~/.hermes/.env

# Add at minimum an LLM provider key:
echo 'OPENROUTER_API_KEY=sk-or-v1-your-key' >> ~/.hermes/.env

รัน (Run)

# Symlink for global access
mkdir -p ~/.local/bin
ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes

# Verify
hermes doctor
hermes chat -q "Hello"

รัน Tests (Run Tests)

pytest tests/ -v

Code Style

  • PEP 8 พร้อมข้อยกเว้นในทางปฏิบัติ (ไม่บังคับความยาวบรรทัดที่เข้มงวด)
  • Comments: เฉพาะเมื่ออธิบายเจตนาที่ไม่ได้ชัดเจน, trade-offs, หรือความแปลกของ API
  • Error handling: ดักจับ exceptions เฉพาะเจาะจง ใช้ logger.warning()/logger.error() พร้อม exc_info=True สำหรับข้อผิดพลาดที่ไม่คาดคิด
  • Cross-platform: ห้ามสมมติว่าเป็น Unix (ดูด้านล่าง)
  • Profile-safe paths: ห้าม hardcode ~/.hermes - ให้ใช้ get_hermes_home() จาก hermes_constants สำหรับ code paths และ display_hermes_home() สำหรับข้อความที่แสดงต่อผู้ใช้ ดู AGENTS.md สำหรับกฎทั้งหมด

ความเข้ากันได้ข้ามแพลตฟอร์ม (Cross-Platform Compatibility)

Hermes รองรับ Linux, macOS, และ WSL2 อย่างเป็นทางการ ไม่รองรับ Windows แบบ Native แต่ codebase มีรูปแบบการเขียนโค้ดป้องกันบางอย่างเพื่อหลีกเลี่ยงการล่มอย่างรุนแรงในกรณีขอบเขต (edge cases) กฎหลัก:

1. termios และ fcntl ใช้ได้เฉพาะบน Unix เท่านั้น

ต้องดักจับทั้ง ImportError และ NotImplementedError เสมอ:

try:
    from simple_term_menu import TerminalMenu
    menu = TerminalMenu(options)
    idx = menu.show()
except (ImportError, NotImplementedError):
    # Fallback: numbered menu
    for i, opt in enumerate(options):
        print(f"  {i+1}. {opt}")
    idx = int(input("Choice: ")) - 1

2. File encoding

สภาพแวดล้อมบางแห่งอาจบันทึกไฟล์ .env ด้วย encoding ที่ไม่ใช่ UTF-8:

try:
    load_dotenv(env_path)
except UnicodeDecodeError:
    load_dotenv(env_path, encoding="latin-1")

3. Process management

os.setsid(), os.killpg(), และ signal handling แตกต่างกันไปในแต่ละแพลตฟอร์ม:

import platform
if platform.system() != "Windows":
    kwargs["preexec_fn"] = os.setsid

4. Path separators

ใช้ pathlib.Path แทนการต่อ string ด้วย /

ข้อควรพิจารณาด้านความปลอดภัย (Security Considerations)

Hermes มีการเข้าถึง terminal ดังนั้นเรื่องความปลอดภัยจึงสำคัญ

การป้องกันที่มีอยู่ (Existing Protections)

LayerImplementation
Sudo password pipingใช้ shlex.quote() เพื่อป้องกัน shell injection
Dangerous command detectionRegex patterns ใน tools/approval.py พร้อม flow การอนุมัติจากผู้ใช้
Cron prompt injectionScanner จะบล็อกรูปแบบการเขียนทับคำสั่ง (instruction-override patterns)
Write deny listProtected paths ที่ถูก resolve ผ่าน os.path.realpath() เพื่อป้องกันการข้าม symlink
Skills guardSecurity scanner สำหรับ skills ที่ติดตั้งใน hub
Code execution sandboxChild process จะรันโดยที่ API keys ถูกลบออกไป
Container hardeningDocker: ปลดความสามารถทั้งหมด (all capabilities dropped), ไม่มี privilege escalation, มี PID limits

การมีส่วนร่วมโค้ดที่อ่อนไหวต่อความปลอดภัย (Contributing Security-Sensitive Code)

  • ใช้ shlex.quote() เสมอเมื่อแทรก user input เข้าไปใน shell commands
  • Resolve symlinks ด้วย os.path.realpath() ก่อนการตรวจสอบ access control
  • ห้าม log secrets
  • ดักจับ exceptions ทั่วไปรอบๆ tool execution
  • ทดสอบบนทุกแพลตฟอร์มหากการเปลี่ยนแปลงของคุณเกี่ยวข้องกับ file paths หรือ processes

กระบวนการ Pull Request (Pull Request Process)

การตั้งชื่อ Branch (Branch Naming)

fix/description        # Bug fixes
feat/description       # New features
docs/description       # Documentation
test/description       # Tests
refactor/description   # Code restructuring

ก่อนการส่ง (Before Submitting)

  1. Run tests: pytest tests/ -v
  2. Test manually: รัน hermes และทดสอบ code path ที่คุณเปลี่ยนแปลง
  3. Check cross-platform impact: พิจารณา macOS และ Linux distros ต่างๆ
  4. Keep PRs focused: หนึ่ง PR ต่อการเปลี่ยนแปลงเชิงตรรกะหนึ่งอย่าง

คำอธิบาย PR (PR Description)

ต้องรวม:

  • อะไร ที่เปลี่ยนไป และ ทำไม
  • วิธีทดสอบ
  • แพลตฟอร์มใด ที่คุณทดสอบ
  • อ้างอิงถึง issues ที่เกี่ยวข้อง

ข้อความ Commit (Commit Messages)

เราใช้ Conventional Commits:

<type>(<scope>): <description>
TypeUse for
fixBug fixes
featNew features
docsDocumentation
testTests
refactorCode restructuring
choreBuild, CI, dependency updates

Scopes: cli, gateway, tools, skills, agent, install, whatsapp, security

ตัวอย่าง:

fix(cli): prevent crash in save_config_value when model is a string
feat(gateway): add WhatsApp multi-user session isolation
fix(security): prevent shell injection in sudo password piping

การรายงานปัญหา (Reporting Issues)

  • ใช้ GitHub Issues
  • ต้องรวม: OS, Python version, Hermes version (hermes version), full error traceback
  • ต้องรวมขั้นตอนในการทำซ้ำ (steps to reproduce)
  • ตรวจสอบ issues ที่มีอยู่ก่อนสร้าง duplicate
  • สำหรับช่องโหว่ด้านความปลอดภัย โปรดรายงานแบบส่วนตัว

ชุมชน (Community)

  • Discord: discord.gg/NousResearch
  • GitHub Discussions: สำหรับข้อเสนอการออกแบบและการพูดคุยสถาปัตยกรรม
  • Skills Hub: อัปโหลด skills เฉพาะทางและแบ่งปันกับชุมชน

License

ด้วยการมีส่วนร่วม คุณตกลงว่าการมีส่วนร่วมของคุณจะอยู่ภายใต้ MIT License


📄 developer-guide/creating-skills.md


sidebar_position: 3 title: "Creating Skills" description: "How to create skills for Hermes Agent — SKILL.md format, guidelines, and publishing"

การสร้าง Skills

Skills คือวิธีที่แนะนำในการเพิ่มความสามารถใหม่ๆ ให้กับ Hermes Agent พวกมันสร้างง่ายกว่า tools ไม่ต้องมีการเปลี่ยนแปลงโค้ดใดๆ ใน agent และสามารถแชร์กับ community ได้

ควรเป็น Skill หรือ Tool?

ให้เป็น Skill เมื่อ:

  • ความสามารถนั้นสามารถแสดงเป็นชุดคำสั่ง + shell commands + tools ที่มีอยู่ได้
  • มันห่อหุ้ม CLI หรือ API ภายนอกที่ agent สามารถเรียกใช้ผ่าน terminal หรือ web_extract
  • ไม่จำเป็นต้องมีการผสานรวม Python หรือการจัดการ API key ที่กำหนดเองฝังอยู่ใน agent
  • ตัวอย่าง: การค้นหา arXiv, git workflows, การจัดการ Docker, การประมวลผล PDF, อีเมลผ่าน CLI tools

ให้เป็น Tool เมื่อ:

  • มันต้องการการผสานรวมแบบ end-to-end ด้วย API keys, auth flows, หรือการกำหนดค่าหลายส่วนประกอบ
  • มันต้องการตรรกะการประมวลผลที่กำหนดเองซึ่งต้องดำเนินการอย่างแม่นยำทุกครั้ง
  • มันจัดการข้อมูลไบนารี, streaming, หรือเหตุการณ์แบบ real-time
  • ตัวอย่าง: browser automation, TTS, vision analysis

โครงสร้าง Directory ของ Skill

Skills ที่รวมไว้จะอยู่ใน skills/ โดยจัดระเบียบตามหมวดหมู่ Skills ทางเลือกอย่างเป็นทางการจะใช้โครงสร้างเดียวกันใน optional-skills/:

skills/
├── research/
│   └── arxiv/
│       ├── SKILL.md              # Required: main instructions
│       └── scripts/              # Optional: helper scripts
│           └── search_arxiv.py
├── productivity/
│   └── ocr-and-documents/
│       ├── SKILL.md
│       ├── scripts/
│       └── references/
└── ...

รูปแบบ SKILL.md

---
name: my-skill
description: Brief description (shown in skill search results)
version: 1.0.0
author: Your Name
license: MIT
platforms: [macos, linux]          # Optional - restrict to specific OS platforms
                                  #   Valid: macos, linux, windows
                                  #   Omit to load on all platforms (default)
metadata:
  hermes:
    tags: [Category, Subcategory, Keywords]
    related_skills: [other-skill-name]
    requires_toolsets: [web]            # Optional - only show when these toolsets are active
    requires_tools: [web_search]        # Optional - only show when these tools are available
    fallback_for_toolsets: [browser]    # Optional - hide when these toolsets are active
    fallback_for_tools: [browser_navigate]  # Optional - hide when these tools exist
    config:                              # Optional - config.yaml settings the skill needs
      - key: my.setting
        description: "What this setting controls"
        default: "sensible-default"
        prompt: "Display prompt for setup"
required_environment_variables:          # Optional - env vars the skill needs
  - name: MY_API_KEY
    prompt: "Enter your API key"
    help: "Get one at https://example.com"
    required_for: "API access"
---

# Skill Title

Brief intro.

## When to Use
Trigger conditions - when should the agent load this skill?

## Quick Reference
Table of common commands or API calls.

## Procedure
Step-by-step instructions the agent follows.

## Pitfalls
Known failure modes and how to handle them.

## Verification
How the agent confirms it worked.

Skills เฉพาะ Platform

Skills สามารถจำกัดตัวเองให้ทำงานบนระบบปฏิบัติการที่เฉพาะเจาะจงได้โดยใช้ฟิลด์ platforms:

platforms: [macos]            # macOS เท่านั้น (เช่น iMessage, Apple Reminders)
platforms: [macos, linux]     # macOS และ Linux
platforms: [windows]          # Windows เท่านั้น

เมื่อกำหนดค่าแล้ว skill จะถูกซ่อนโดยอัตโนมัติจาก system prompt, skills_list(), และ slash commands บนแพลตฟอร์มที่ไม่รองรับ หากละเว้นหรือว่างเปล่า skill จะโหลดบนทุกแพลตฟอร์ม (รองรับย้อนหลัง)

การเปิดใช้งาน Skill แบบมีเงื่อนไข

Skills สามารถประกาศการพึ่งพา (dependencies) บน tools หรือ toolsets ที่เฉพาะเจาะจงได้ สิ่งนี้จะควบคุมว่า skill จะปรากฏใน system prompt สำหรับ session นั้นๆ หรือไม่

metadata:
  hermes:
    requires_toolsets: [web]           # ซ่อนหาก web toolset ไม่ทำงาน
    requires_tools: [web_search]       # ซ่อนหาก web_search tool ไม่พร้อมใช้งาน
    fallback_for_toolsets: [browser]   # ซ่อนหาก browser toolset ทำงานอยู่
    fallback_for_tools: [browser_navigate]  # ซ่อนหาก browser_navigate มีอยู่
FieldBehavior
requires_toolsetsSkill จะ ถูกซ่อน เมื่อ toolset ที่ระบุรายการใดรายการหนึ่ง ไม่ พร้อมใช้งาน
requires_toolsSkill จะ ถูกซ่อน เมื่อ tool ที่ระบุรายการใดรายการหนึ่ง ไม่ พร้อมใช้งาน
fallback_for_toolsetsSkill จะ ถูกซ่อน เมื่อ toolset ที่ระบุรายการใดรายการหนึ่ง ทำงาน อยู่
fallback_for_toolsSkill จะ ถูกซ่อน เมื่อ tool ที่ระบุรายการใดรายการหนึ่ง ทำงาน อยู่

กรณีการใช้งานสำหรับ fallback_for_*: สร้าง skill ที่ทำหน้าที่เป็น workaround เมื่อ tool หลักไม่พร้อมใช้งาน ตัวอย่างเช่น skill duckduckgo-search ที่มี fallback_for_tools: [web_search] จะแสดงก็ต่อเมื่อ web search tool (ซึ่งต้องใช้ API key) ไม่ได้ถูกกำหนดค่าไว้

กรณีการใช้งานสำหรับ requires_*: สร้าง skill ที่สมเหตุสมผลเมื่อมี tools บางอย่างอยู่ ตัวอย่างเช่น skill workflow สำหรับ web scraping ที่มี requires_toolsets: [web] จะไม่ทำให้ prompt รกเมื่อ web tools ถูกปิดใช้งาน

ข้อกำหนด Environment Variable

Skills สามารถประกาศ environment variables ที่ต้องการได้ เมื่อ skill ถูกโหลดผ่าน skill_view ตัวแปรที่จำเป็นจะถูกลงทะเบียนโดยอัตโนมัติสำหรับการส่งผ่าน (passthrough) เข้าสู่ sandboxed execution environments (terminal, execute_code)

required_environment_variables:
  - name: TENOR_API_KEY
    prompt: "Tenor API key"               # แสดงเมื่อถามผู้ใช้
    help: "Get your key at https://tenor.com"  # ข้อความช่วยเหลือหรือ URL
    required_for: "GIF search functionality"   # สิ่งที่ต้องการตัวแปรนี้

แต่ละรายการรองรับ:

  • name (required) - ชื่อ environment variable
  • prompt (optional) - ข้อความ prompt เมื่อขอค่าจากผู้ใช้
  • help (optional) - ข้อความช่วยเหลือหรือ URL สำหรับรับค่า
  • required_for (optional) - อธิบายว่าฟีเจอร์ใดต้องการตัวแปรนี้

ผู้ใช้ยังสามารถกำหนดค่าตัวแปร passthrough ด้วยตนเองใน config.yaml:

terminal:
  env_passthrough:
    - MY_CUSTOM_VAR
    - ANOTHER_VAR

ดูที่ skills/apple/ สำหรับตัวอย่าง skills ที่ใช้ได้เฉพาะ macOS

การตั้งค่าที่ปลอดภัยเมื่อโหลด

ใช้ required_environment_variables เมื่อ skill ต้องการ API key หรือ token ค่าที่ขาดหายไป จะไม่ ซ่อน skill จากการค้นพบ แต่ Hermes จะแจ้งให้ผู้ใช้ทราบอย่างปลอดภัยเมื่อ skill ถูกโหลดใน local CLI แทน

required_environment_variables:
  - name: TENOR_API_KEY
    prompt: Tenor API key
    help: Get a key from https://developers.google.com/tenor
    required_for: full functionality

ผู้ใช้สามารถข้ามการตั้งค่าและยังคงโหลด skill ได้ Hermes จะไม่เปิดเผยค่า secret ดิบให้ model ดู Gateway และ messaging sessions จะแสดงคำแนะนำการตั้งค่าในเครื่องแทนการรวบรวม secrets ใน-band

:::tip Sandbox Passthrough เมื่อ skill ของคุณถูกโหลด required_environment_variables ที่ประกาศไว้และถูกตั้งค่าแล้วจะถูก ส่งผ่านโดยอัตโนมัติ ไปยัง execute_code และ terminal sandboxes รวมถึง backends ระยะไกลอย่าง Docker และ Modal สคริปต์ของ skill ของคุณสามารถเข้าถึง $TENOR_API_KEY (หรือ os.environ["TENOR_API_KEY"] ใน Python) โดยที่ผู้ใช้ไม่จำเป็นต้องกำหนดค่าอะไรเพิ่มเติม ดู Environment Variable Passthrough สำหรับรายละเอียด :::

prerequisites.env_vars แบบเก่า ยังคงรองรับในฐานะ alias ที่รองรับย้อนหลัง

การตั้งค่า Config (config.yaml)

Skills สามารถประกาศการตั้งค่าที่ไม่ใช่ secret ซึ่งจะถูกจัดเก็บใน config.yaml ภายใต้ namespace skills.config แตกต่างจาก environment variables (ซึ่งเป็น secrets ที่จัดเก็บใน .env) การตั้งค่า config มีไว้สำหรับ paths, preferences, และค่าอื่นๆ ที่ไม่ละเอียดอ่อน

metadata:
  hermes:
    config:
      - key: myplugin.path
        description: Path to the plugin data directory
        default: "~/myplugin-data"
        prompt: Plugin data directory path
      - key: myplugin.domain
        description: Domain the plugin operates on
        default: ""
        prompt: Plugin domain (e.g., AI/ML research)

แต่ละรายการรองรับ:

  • key (required) - dotpath สำหรับการตั้งค่า (เช่น myplugin.path)
  • description (required) - อธิบายว่าการตั้งค่านี้ควบคุมอะไร
  • default (optional) - ค่าเริ่มต้นหากผู้ใช้ไม่ได้กำหนดค่า
  • prompt (optional) - ข้อความ prompt ที่แสดงระหว่าง hermes config migrate; จะ fallback ไปที่ description

วิธีการทำงาน:

  1. การจัดเก็บ: ค่าจะถูกเขียนลงใน config.yaml ภายใต้ skills.config.<key>:

    skills:
      config:
        myplugin:
          path: ~/my-data
  2. การค้นพบ: hermes config migrate จะสแกน skills ที่เปิดใช้งานทั้งหมด ค้นหาการตั้งค่าที่ยังไม่ได้กำหนดค่า และแจ้งให้ผู้ใช้ทราบ การตั้งค่ายังปรากฏใน hermes config show ภายใต้ "Skill Settings."

  3. การฉีดค่าขณะรันไทม์: เมื่อ skill โหลด ค่า config ของมันจะถูกแก้ไขและแนบไปกับข้อความ skill:

    [Skill config (from ~/.hermes/config.yaml):
      myplugin.path = /home/user/my-data
    ]

    agent จะเห็นค่าที่กำหนดค่าแล้วโดยไม่จำเป็นต้องอ่าน config.yaml เอง

  4. การตั้งค่าด้วยตนเอง: ผู้ใช้ยังสามารถตั้งค่าค่าได้โดยตรง:

    hermes config set skills.config.myplugin.path ~/my-data

:::tip เมื่อไหร่ควรใช้ตัวไหน ใช้ required_environment_variables สำหรับ API keys, tokens, และ secrets อื่นๆ (จัดเก็บใน ~/.hermes/.env, จะไม่แสดงให้ model เห็น) ใช้ config สำหรับ paths, preferences, และการตั้งค่าที่ไม่ละเอียดอ่อน (จัดเก็บใน config.yaml, แสดงใน config show) :::

ข้อกำหนด Credential File (OAuth tokens, ฯลฯ)

Skills ที่ใช้ OAuth หรือ credentials แบบไฟล์ สามารถประกาศไฟล์ที่ต้องถูก mount เข้าไปใน remote sandboxes ได้ นี่มีไว้สำหรับ credentials ที่จัดเก็บเป็น ไฟล์ (ไม่ใช่ env vars) - โดยทั่วไปคือไฟล์ OAuth token ที่สร้างโดย setup script

required_credential_files:
  - path: google_token.json
    description: Google OAuth2 token (created by setup script)
  - path: google_client_secret.json
    description: Google OAuth2 client credentials

แต่ละรายการรองรับ:

  • path (required) - file path เทียบกับ ~/.hermes/
  • description (optional) - อธิบายว่าไฟล์คืออะไรและสร้างขึ้นได้อย่างไร

เมื่อโหลด Hermes จะตรวจสอบว่าไฟล์เหล่านี้มีอยู่หรือไม่ ไฟล์ที่ขาดหายไปจะกระตุ้น setup_needed ไฟล์ที่มีอยู่จะถูก:

  • Mount เข้าไปใน Docker containers เป็น read-only bind mounts
  • Sync เข้าไปใน Modal sandboxes (เมื่อสร้าง + ก่อนแต่ละคำสั่ง ดังนั้น OAuth กลาง session จึงใช้งานได้)
  • พร้อมใช้งานบน backend local โดยไม่มีการจัดการพิเศษใดๆ

:::tip เมื่อไหร่ควรใช้ตัวไหน ใช้ required_environment_variables สำหรับ API keys และ tokens อย่างง่าย (strings ที่จัดเก็บใน ~/.hermes/.env) ใช้ required_credential_files สำหรับไฟล์ OAuth token, client secrets, service account JSON, certificates, หรือ credentials ใดๆ ที่เป็นไฟล์บน disk :::

ดูที่ skills/productivity/google-workspace/SKILL.md สำหรับตัวอย่างที่สมบูรณ์โดยใช้ทั้งสองอย่าง

แนวทางปฏิบัติสำหรับ Skill

ไม่มี External Dependencies

ควรใช้ stdlib Python, curl, และ tools ของ Hermes ที่มีอยู่ (web_extract, terminal, read_file) หากจำเป็นต้องมี dependency ให้บันทึกขั้นตอนการติดตั้งใน skill

Progressive Disclosure

ให้วาง workflow ที่พบบ่อยที่สุดไว้ก่อน กรณีขอบ (edge cases) และการใช้งานขั้นสูงให้ไว้ด้านล่าง สิ่งนี้ช่วยให้การใช้ token สำหรับงานทั่วไปต่ำ

รวม Helper Scripts

สำหรับการ parse XML/JSON หรือตรรกะที่ซับซ้อน ให้รวม helper scripts ใน scripts/ - อย่าคาดหวังให้ LLM เขียน parsers แบบ inline ทุกครั้ง

การอ้างอิง scripts ที่รวมไว้จาก SKILL.md

เมื่อ skill ถูกโหลด ข้อความ activation จะเปิดเผย directory ของ skill แบบ absolute เป็น [Skill directory: /abs/path] และยังแทนที่ template tokens สองตัวได้ทุกที่ในเนื้อหา SKILL.md:

TokenReplaced with
${HERMES_SKILL_DIR}Absolute path ไปยัง directory ของ skill
${HERMES_SESSION_ID}active session id (คงไว้หากไม่มี session)

ดังนั้น SKILL.md สามารถบอก agent ให้รัน bundled script โดยตรงด้วย:

To analyse the input, run:

    node ${HERMES_SKILL_DIR}/scripts/analyse.js <input>

agent จะเห็น absolute path ที่ถูกแทนที่และเรียกใช้ tool terminal ด้วยคำสั่งที่พร้อมรัน - ไม่ต้องคำนวณ path, ไม่ต้อง round-trip skill_view เพิ่มเติม สามารถปิดการแทนที่ทั่วโลกได้ด้วย skills.template_vars: false ใน config.yaml

Inline shell snippets (opt-in)

Skills ยังสามารถฝัง inline shell snippets ที่เขียนเป็น !`cmd` ในเนื้อหา SKILL.md ได้ เมื่อเปิดใช้งาน stdout ของ snippet แต่ละตัวจะถูกรวมเข้ากับข้อความก่อนที่ agent จะอ่าน ทำให้ skills สามารถฉีด dynamic context ได้:

Current date: !`date -u +%Y-%m-%d`
Git branch: !`git -C ${HERMES_SKILL_DIR} rev-parse --abbrev-ref HEAD`

สิ่งนี้ ปิดอยู่โดยค่าเริ่มต้น - snippet ใดๆ ใน SKILL.md จะรันบน host โดยไม่มีการอนุมัติ ดังนั้นให้เปิดใช้งานเฉพาะสำหรับแหล่งที่มาของ skill ที่คุณเชื่อถือเท่านั้น:

# config.yaml
skills:
  inline_shell: true
  inline_shell_timeout: 10   # seconds per snippet

Snippets รันด้วย skill directory เป็น working directory และ output ถูกจำกัดที่ 4000 characters ความล้มเหลว (timeouts, non-zero exits) จะแสดงเป็น marker สั้นๆ [inline-shell error: ...] แทนที่จะทำให้ skill ทั้งหมดล้มเหลว

การทดสอบ

รัน skill และตรวจสอบว่า agent ทำตามคำแนะนำอย่างถูกต้อง:

hermes chat --toolsets skills -q "Use the X skill to do Y"

Skill ควรอยู่ตรงไหน?

Skills ที่รวมไว้ (ใน skills/) มาพร้อมกับการติดตั้ง Hermes ทุกครั้ง พวกมันควรมี ประโยชน์ในวงกว้างสำหรับผู้ใช้ส่วนใหญ่:

  • การจัดการเอกสาร, web research, common dev workflows, system administration
  • ถูกใช้งานเป็นประจำโดยผู้คนหลากหลายกลุ่ม

หาก skill ของคุณเป็น official และมีประโยชน์แต่ไม่ได้จำเป็นสำหรับทุกคน (เช่น การผสานรวมบริการแบบเสียเงิน, dependency ที่มีน้ำหนักมาก) ให้ใส่ไว้ใน optional-skills/ - มันจะมาพร้อมกับ repo, สามารถค้นพบได้ผ่าน hermes skills browse (ติดป้าย "official"), และติดตั้งด้วยความเชื่อถือในตัว

หาก skill ของคุณมีความเฉพาะทาง, มีการสนับสนุนจาก community, หรือเป็น niche, จะเหมาะกับ Skills Hub มากกว่า - อัปโหลดไปยัง registry และแชร์ผ่าน hermes skills install

การเผยแพร่ Skills

ไปยัง Skills Hub

hermes skills publish skills/my-skill --to github --repo owner/repo

ไปยัง Custom Repository

เพิ่ม repo ของคุณเป็น tap:

hermes skills tap add owner/repo

ผู้ใช้สามารถค้นหาและติดตั้งจาก repository ของคุณได้

Security Scanning

skills ทั้งหมดที่ติดตั้งผ่าน hub จะผ่าน security scanner ที่ตรวจสอบ:

  • Data exfiltration patterns
  • Prompt injection attempts
  • Destructive commands
  • Shell injection

ระดับความเชื่อถือ:

  • builtin - มาพร้อมกับ Hermes (เชื่อถือได้เสมอ)
  • official - จาก optional-skills/ ใน repo (builtin trust, ไม่มีคำเตือน third-party)
  • trusted - จาก openai/skills, anthropics/skills
  • community - ผลลัพธ์ที่ไม่เป็นอันตรายสามารถถูก override ด้วย --force; ผลลัพธ์ dangerous ยังคงถูกบล็อก

ตอนนี้ Hermes สามารถใช้ third-party skills จาก multiple external discovery models:

  • direct GitHub identifiers (เช่น openai/skills/k8s)
  • skills.sh identifiers (เช่น skills-sh/vercel-labs/json-render/json-render-react)
  • well-known endpoints ที่ให้บริการจาก /.well-known/skills/index.json

หากคุณต้องการให้ skills ของคุณสามารถค้นพบได้โดยไม่จำเป็นต้องมี installer เฉพาะ GitHub ให้พิจารณาให้บริการจาก well-known endpoint นอกเหนือจากการเผยแพร่ใน repo หรือ marketplace


📄 developer-guide/cron-internals.md


sidebar_position: 11 title: "Cron Internals" description: "How Hermes stores, schedules, edits, pauses, skill-loads, and delivers cron jobs"

Cron Internals

ระบบ cron subsystem ให้การทำงานตามตารางเวลา (scheduled task execution) ตั้งแต่การหน่วงเวลาแบบครั้งเดียว (one-shot delays) ไปจนถึงงานแบบ cron-expression ที่มีการทำซ้ำ (recurring) พร้อมการฉีด skill และการส่งมอบข้ามแพลตฟอร์ม

Key Files

FilePurpose
cron/jobs.pyJob model, storage, atomic read/write to jobs.json
cron/scheduler.pyScheduler loop - due-job detection, execution, repeat tracking
tools/cronjob_tools.pyModel-facing cronjob tool registration and handler
gateway/run.pyGateway integration - cron ticking in the long-running loop
hermes_cli/cron.pyCLI hermes cron subcommands

Scheduling Model

รองรับรูปแบบการกำหนดเวลา 4 รูปแบบ:

FormatExampleBehavior
Relative delay30m, 2h, 1dOne-shot, fires after the specified duration
Intervalevery 2h, every 30mRecurring, fires at regular intervals
Cron expression0 9 * * *Standard 5-field cron syntax (minute, hour, day, month, weekday)
ISO timestamp2025-01-15T09:00:00One-shot, fires at the exact time

ส่วนที่เปิดให้ใช้งาน (surface) สำหรับ model คือ cronjob tool ตัวเดียว ที่มี operations แบบ action: create, list, update, pause, resume, run, remove

Job Storage

Jobs ถูกจัดเก็บใน ~/.hermes/cron/jobs.json ด้วย semantics การเขียนแบบ atomic (เขียนไปยัง temp file แล้วจึงเปลี่ยนชื่อ) แต่ละ record ของ job ประกอบด้วย:

{
  "id": "a1b2c3d4e5f6",
  "name": "Daily briefing",
  "prompt": "Summarize today's AI news and funding rounds",
  "schedule": {
    "kind": "cron",
    "expr": "0 9 * * *",
    "display": "0 9 * * *"
  },
  "skills": ["ai-funding-daily-report"],
  "deliver": "telegram:-1001234567890",
  "repeat": {
    "times": null,
    "completed": 42
  },
  "state": "scheduled",
  "enabled": true,
  "next_run_at": "2025-01-16T09:00:00Z",
  "last_run_at": "2025-01-15T09:00:00Z",
  "last_status": "ok",
  "created_at": "2025-01-01T00:00:00Z",
  "model": null,
  "provider": null,
  "script": null
}

Job Lifecycle States

StateMeaning
scheduledActive, will fire at next scheduled time
pausedSuspended - won't fire until resumed
completedRepeat count exhausted or one-shot that has fired
runningCurrently executing (transient state)

Backward Compatibility

Jobs เก่าอาจมี field skill เพียงตัวเดียวแทนที่จะเป็น array skills Scheduler จะทำการ normalize สิ่งนี้ในขณะโหลด - single skill จะถูก promote เป็น skills: [skill]

Scheduler Runtime

Tick Cycle

Scheduler ทำงานเป็นรอบ tick ตามช่วงเวลา (ค่าเริ่มต้น: ทุก 60 วินาที):

tick()
  1. Acquire scheduler lock (prevents overlapping ticks)
  2. Load all jobs from jobs.json
  3. Filter to due jobs (next_run <= now AND state == "scheduled")
  4. For each due job:
     a. Set state to "running"
     b. Create fresh AIAgent session (no conversation history)
     c. Load attached skills in order (injected as user messages)
     d. Run the job prompt through the agent
     e. Deliver the response to the configured target
     f. Update run_count, compute next_run
     g. If repeat count exhausted → state = "completed"
     h. Otherwise → state = "scheduled"
  5. Write updated jobs back to jobs.json
  6. Release scheduler lock

Gateway Integration

ในโหมด gateway, scheduler tick จะถูกรวมเข้ากับ main event loop ของ gateway Gateway จะเรียกใช้ scheduler.tick() ในรอบการบำรุงรักษาตามช่วงเวลา ซึ่งทำงานควบคู่ไปกับการจัดการข้อความ

ในโหมด CLI, cron jobs จะทำงานก็ต่อเมื่อมีการรันคำสั่ง hermes cron หรือระหว่าง active CLI sessions เท่านั้น

Fresh Session Isolation

แต่ละ cron job จะทำงานใน agent session ที่สดใหม่โดยสมบูรณ์:

  • ไม่มี conversation history จากการรันครั้งก่อนหน้า
  • ไม่มี memory ของการ execute cron ครั้งก่อนหน้า (เว้นแต่จะถูก persist ไปยัง memory/files)
  • Prompt ต้องเป็น self-contained - cron jobs ไม่สามารถถามคำถามเพื่อขอความชัดเจนได้
  • cronjob toolset ถูกปิดใช้งาน (recursion guard)

Skill-Backed Jobs

cron job สามารถแนบ skill หนึ่งตัวหรือมากกว่าผ่าน field skills เมื่อถึงเวลา execute:

  1. Skills จะถูกโหลดตามลำดับที่กำหนด
  2. เนื้อหา SKILL.md ของแต่ละ skill จะถูกฉีดเป็น context
  3. Prompt ของ job จะถูกต่อท้ายเป็นคำสั่งงาน
  4. Agent จะประมวลผล combined skill context + prompt

สิ่งนี้ช่วยให้สามารถสร้าง workflow ที่นำกลับมาใช้ใหม่ได้และผ่านการทดสอบ โดยไม่ต้องวางคำสั่งทั้งหมดลงใน cron prompts ตัวอย่างเช่น:

Create a daily funding report → attach "ai-funding-daily-report" skill

Script-Backed Jobs

Jobs ยังสามารถแนบ Python script ผ่าน field script ได้ Script จะทำงาน ก่อน แต่ละ agent turn และ stdout ของ script จะถูกฉีดเข้าไปใน prompt เป็น context สิ่งนี้ช่วยให้สามารถทำ data collection และ change detection patterns:

# ~/.hermes/scripts/check_competitors.py
import requests, json
# Fetch competitor release notes, diff against last run
# Print summary to stdout - agent analyzes and reports

Script timeout จะตั้งค่าเริ่มต้นที่ 120 วินาที ฟังก์ชัน _get_script_timeout() จะหาขีดจำกัดผ่าน chain สามชั้น:

  1. Module-level override - _SCRIPT_TIMEOUT (สำหรับ tests/monkeypatching) ใช้เฉพาะเมื่อค่าแตกต่างจากค่าเริ่มต้นเท่านั้น
  2. Environment variable - HERMES_CRON_SCRIPT_TIMEOUT
  3. Config - cron.script_timeout_seconds ใน config.yaml (อ่านผ่าน load_config())
  4. Default - 120 seconds

Provider Recovery

run_job() จะส่ง fallback providers และ credential pool ที่กำหนดค่าโดยผู้ใช้เข้าสู่ instance AIAgent:

  • Fallback providers - อ่านจาก fallback_providers (list) หรือ fallback_model (legacy dict) จาก config.yaml โดยตรงกับรูปแบบ _load_fallback_model() ของ gateway ส่งเป็น fallback_model= ไปยัง AIAgent.__init__ ซึ่งจะ normalize ทั้งสองรูปแบบให้เป็น fallback chain
  • Credential pool - โหลดผ่าน load_pool(provider) จาก agent.credential_pool โดยใช้ชื่อ provider runtime ที่ resolve แล้ว จะถูกส่งก็ต่อเมื่อ pool มี credentials (pool.has_credentials()) ช่วยให้สามารถหมุนเวียน key ของ provider เดียวกันได้เมื่อเกิดข้อผิดพลาด 429/rate-limit

สิ่งนี้สะท้อนพฤติกรรมของ gateway - หากไม่มีส่วนนี้ cron agents จะล้มเหลวเมื่อเกิด rate limits โดยไม่ได้พยายามกู้คืน

Delivery Model

ผลลัพธ์ของ cron job สามารถส่งไปยังแพลตฟอร์มที่รองรับได้ทุกประเภท:

TargetSyntaxExample
Origin chatoriginDeliver to the chat where the job was created
Local filelocalSave to ~/.hermes/cron/output/
Telegramtelegram หรือ telegram:<chat_id>telegram:-1001234567890
Discorddiscord หรือ discord:#channeldiscord:#engineering
SlackslackDeliver to Slack home channel
WhatsAppwhatsappDeliver to WhatsApp home
SignalsignalDeliver to Signal
MatrixmatrixDeliver to Matrix home room
MattermostmattermostDeliver to Mattermost home
EmailemailDeliver via email
SMSsmsDeliver via SMS
Home AssistanthomeassistantDeliver to HA conversation
DingTalkdingtalkDeliver to DingTalk
FeishufeishuDeliver to Feishu
WeComwecomDeliver to WeCom
WeixinweixinDeliver to Weixin (WeChat)
BlueBubblesbluebubblesDeliver to iMessage via BlueBubbles
QQ BotqqbotDeliver to QQ (Tencent) via Official API v2

สำหรับหัวข้อของ Telegram ให้ใช้รูปแบบ telegram:<chat_id>:<thread_id> (เช่น telegram:-1001234567890:17585)

Response Wrapping

โดยค่าเริ่มต้น (cron.wrap_response: true), การส่งมอบของ cron จะถูกห่อด้วย:

  • Header ที่ระบุชื่อ cron job และ task
  • Footer ที่แจ้งว่า agent ไม่สามารถเห็นข้อความที่ส่งมอบใน conversation ได้

Prefix [SILENT] ใน cron response จะระงับการส่งมอบโดยสมบูรณ์ - มีประโยชน์สำหรับ jobs ที่ต้องการเขียนไปยังไฟล์เท่านั้นหรือทำ side effects

Session Isolation

Cron deliveries จะไม่ถูกสะท้อน (mirrored) เข้าไปใน gateway session conversation history พวกมันมีอยู่เฉพาะใน session ของ cron job เองเท่านั้น สิ่งนี้ป้องกันการละเมิด message alternation ใน conversation ของ target chat

Recursion Guard

Session ที่รัน cron จะปิดใช้งาน cronjob toolset สิ่งนี้ป้องกัน:

  • การที่ scheduled job สร้าง cron jobs ใหม่
  • การทำ recursive scheduling ที่อาจทำให้ token usage ระเบิด
  • การแก้ไข job schedule โดยไม่ได้ตั้งใจจากภายใน job

Locking

Scheduler ใช้ file-based locking เพื่อป้องกันไม่ให้เกิดการทำงานซ้ำของ batch due-job จากการที่ multiple maintenance cycles ทับซ้อนกัน ซึ่งสำคัญในโหมด gateway ที่รอบการบำรุงรักษาหลายรอบอาจทับซ้อนกันได้หาก tick ก่อนหน้าใช้เวลานานกว่าช่วงเวลาของ tick

CLI Interface

hermes cron CLI ให้การจัดการ job โดยตรง:

hermes cron list                    # Show all jobs
hermes cron create                  # Interactive job creation (alias: add)
hermes cron edit <job_id>           # Edit job configuration
hermes cron pause <job_id>          # Pause a running job
hermes cron resume <job_id>         # Resume a paused job
hermes cron run <job_id>            # Trigger immediate execution
hermes cron remove <job_id>         # Delete a job

Related Docs


extent analysis

TL;DR

To resolve the issue, review the configuration settings for the ContextCompressor and ensure that the threshold and target ratio are properly set, and consider adjusting the protect_last_n parameter to preserve more recent messages.

Guidance

  1. Check configuration settings: Verify that the compression settings in config.yaml are correctly configured, paying attention to threshold, target_ratio, and protect_last_n.
  2. Adjust threshold and target ratio: If necessary, adjust the threshold and target_ratio to balance between context preservation and compression.
  3. Review protect_last_n: Consider increasing protect_last_n to preserve more recent messages and prevent important context from being lost during compression.
  4. Test and iterate: After making changes, test the compression behavior and adjust settings as needed to achieve the desired balance between context preservation and compression efficiency.

Example

No specific code example is provided as the issue seems to be related to configuration rather than code. However, ensuring that the config.yaml contains appropriate settings for compression, such as:

compression:
  enabled: true
  threshold: 0.50
  target_ratio: 0.20
  protect_last_n: 20

can help in managing context compression effectively.

Notes

  • The effectiveness of compression and the preservation of context depend heavily on the specific use case and the nature of the conversations.
  • Regularly reviewing and adjusting compression settings may be necessary as the usage patterns and requirements evolve.

Recommendation

Apply adjustments to the compression settings in config.yaml to better suit the specific needs of the application, focusing on finding an optimal balance between preserving context and efficiently managing conversation history.

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