Compare commits

...

10 commits

Author SHA1 Message Date
9406d26afd the agent and browser are now running in docker 2026-03-27 01:05:42 +03:00
c0b3dd4fb9 edit Dockerfile, docker compose 2026-03-26 12:59:32 +03:00
d4534ccc29 edit Dockerfile, docker-compose.yml, .dockerignore 2026-03-25 16:23:28 +03:00
2261cbf1a0 Add Dockerfile, docker-compose.yml, .dockerignore 2026-03-25 15:20:14 +03:00
Teknium
481915587e
fix: update context pressure warnings and token estimates after compaction
Reset context pressure warnings and update last_prompt_tokens and last_completion_tokens in the context compressor to prevent stale values from causing excessive warnings and re-triggering compression. This change ensures accurate pressure calculations following the compaction process.
2026-03-24 09:25:10 -07:00
Teknium
0b993c1e07
docs: quote pip install extras to fix zsh glob errors (#2815)
zsh interprets square brackets as glob patterns, so
`pip install hermes-agent[voice]` fails with 'no matches found'.
Quote all pip install commands with extras across 5 docs pages (12 instances).

Reported by OFumik0OP.
2026-03-24 09:25:01 -07:00
Teknium
9718334962
docs: fix api-server response storage — SQLite, not in-memory (#2819)
* docs: update all docs for /model command overhaul and custom provider support

Documents the full /model command overhaul across 6 files:

AGENTS.md:
- Add model_switch.py to project structure tree

configuration.md:
- Rewrite General Setup with 3 config methods (interactive, config.yaml, env vars)
- Add new 'Switching Models with /model' section documenting all syntax variants
- Add 'Named Custom Providers' section with config.yaml examples and
  custom:name:model triple syntax

slash-commands.md:
- Update /model descriptions in both CLI and messaging tables with
  full syntax examples (provider:model, custom:model, custom:name:model,
  bare custom auto-detect)

cli-commands.md:
- Add /model slash command subsection under hermes model with syntax table
- Add custom endpoint config to hermes model use cases

faq.md:
- Add config.yaml example for offline/local model setup
- Note that provider: custom is a first-class provider
- Document /model custom auto-detect

provider-runtime.md:
- Add model_switch.py to implementation file list
- Update provider families to show Custom as first-class with named variants

* docs: fix api-server response storage description — SQLite, not in-memory

The ResponseStore class uses SQLite persistence (with in-memory
fallback), not pure in-memory storage. Responses survive gateway
restarts.
2026-03-24 09:05:15 -07:00
Teknium
ebcb81b649
docs: document 9 previously undocumented features
New documentation for features that existed in code but had no docs:

New page:
- context-references.md: Full docs for @-syntax inline context
  injection (@file:, @folder:, @diff, @staged, @git:, @url:) with
  line ranges, CLI autocomplete, size limits, sensitive path blocking,
  and error handling

configuration.md additions:
- Environment variable substitution: ${VAR_NAME} syntax in config.yaml
  with expansion, fallback, and multi-reference support
- Gateway streaming: Progressive token delivery on messaging platforms
  via message editing (StreamingConfig: enabled, transport, edit_interval,
  buffer_threshold, cursor) with platform support matrix
- Web search backends: Three providers (Firecrawl, Parallel, Tavily)
  with web.backend config key, capability matrix, auto-detection from
  API keys, self-hosted Firecrawl, and Parallel search modes

security.md additions:
- SSRF protection: Always-on URL validation blocking private networks,
  loopback, link-local, CGNAT, cloud metadata hostnames, with
  fail-closed DNS and redirect chain re-validation
- Tirith pre-exec security scanning: Content-level command scanning
  for homograph URLs, pipe-to-interpreter, terminal injection with
  auto-install, SHA-256/cosign verification, config options, and
  fail-open/fail-closed modes

sessions.md addition:
- Auto-generated session titles: Background LLM-powered title
  generation after first exchange

creating-skills.md additions:
- Conditional skill activation: requires_toolsets, requires_tools,
  fallback_for_toolsets, fallback_for_tools frontmatter fields with
  matching logic and use cases
- Environment variable requirements: required_environment_variables
  frontmatter for automatic env passthrough to sandboxed execution,
  plus terminal.env_passthrough user config
2026-03-24 08:56:21 -07:00
Teknium
ac5b8a478a
ci: add supply chain audit workflow for PR scanning (#2816)
Scans every PR diff for patterns associated with supply chain attacks:

CRITICAL (blocks merge):
- .pth files (auto-execute on Python startup — litellm attack vector)
- base64 decode + exec/eval combo (obfuscated payload execution)
- subprocess with encoded/obfuscated commands

WARNING (comment only, no block):
- base64 encode/decode alone (legitimate uses: images, JWT, etc.)
- exec/eval alone
- Outbound POST/PUT requests
- setup.py/sitecustomize.py/usercustomize.py changes
- marshal.loads/pickle.loads/compile()

Posts a detailed comment on the PR with matched lines and context.
Excludes lockfiles (uv.lock, package-lock.json) from scanning.

Motivated by the litellm 1.82.7/1.82.8 credential stealer attack
(BerriAI/litellm#24512).
2026-03-24 08:56:04 -07:00
Teknium
624e4a8e7a
chore: regenerate uv.lock with hashes, use lockfile in setup (#2812)
- Regenerate uv.lock with sha256 hashes for all 2965 package artifacts
- Add python_version marker to yc-bench (requires >=3.12)
- Update setup-hermes.sh to prefer 'uv sync --locked' for hash-verified
  installs, with fallback to 'uv pip install' when lockfile is stale

This completes the supply chain hardening: pyproject.toml bounds the
version ranges, and uv.lock pins exact versions with cryptographic
hashes so tampered packages are rejected at install time.
2026-03-24 08:42:45 -07:00
20 changed files with 3097 additions and 470 deletions

17
.dockerignore Normal file
View file

@ -0,0 +1,17 @@
venv/
.venv/
node_modules/
__pycache__/
*.pyc
*.pyo
*.pyd
.git/
.github/
.env
config.yaml
sessions/
logs/
state.db

View file

@ -320,3 +320,7 @@ WANDB_API_KEY=
# Override STT provider endpoints (for proxies or self-hosted instances)
# GROQ_BASE_URL=https://api.groq.com/openai/v1
# STT_OPENAI_BASE_URL=https://api.openai.com/v1
HERMES_DATA_PATH= # Укажите путь к папке .hermes
HERMES_WORKSPACE_PATH= # Укажите путь к воркспейсу гермеса

192
.github/workflows/supply-chain-audit.yml vendored Normal file
View file

@ -0,0 +1,192 @@
name: Supply Chain Audit
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
pull-requests: write
contents: read
jobs:
scan:
name: Scan PR for supply chain risks
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Scan diff for suspicious patterns
id: scan
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
# Get the full diff (added lines only)
DIFF=$(git diff "$BASE".."$HEAD" -- . ':!uv.lock' ':!*.lock' ':!package-lock.json' ':!yarn.lock' || true)
FINDINGS=""
CRITICAL=false
# --- .pth files (auto-execute on Python startup) ---
PTH_FILES=$(git diff --name-only "$BASE".."$HEAD" | grep '\.pth$' || true)
if [ -n "$PTH_FILES" ]; then
CRITICAL=true
FINDINGS="${FINDINGS}
### 🚨 CRITICAL: .pth file added or modified
Python \`.pth\` files in \`site-packages/\` execute automatically when the interpreter starts — no import required. This is the exact mechanism used in the [litellm supply chain attack](https://github.com/BerriAI/litellm/issues/24512).
**Files:**
\`\`\`
${PTH_FILES}
\`\`\`
"
fi
# --- base64 + exec/eval combo (the litellm attack pattern) ---
B64_EXEC_HITS=$(echo "$DIFF" | grep -n '^\+' | grep -iE 'base64\.(b64decode|decodebytes|urlsafe_b64decode)' | grep -iE 'exec\(|eval\(' | head -10 || true)
if [ -n "$B64_EXEC_HITS" ]; then
CRITICAL=true
FINDINGS="${FINDINGS}
### 🚨 CRITICAL: base64 decode + exec/eval combo
This is the exact pattern used in the [litellm supply chain attack](https://github.com/BerriAI/litellm/issues/24512) — base64-decoded strings passed to exec/eval to hide credential-stealing payloads.
**Matches:**
\`\`\`
${B64_EXEC_HITS}
\`\`\`
"
fi
# --- base64 decode/encode (alone — legitimate uses exist) ---
B64_HITS=$(echo "$DIFF" | grep -n '^\+' | grep -iE 'base64\.(b64decode|b64encode|decodebytes|encodebytes|urlsafe_b64decode)|atob\(|btoa\(|Buffer\.from\(.*base64' | head -20 || true)
if [ -n "$B64_HITS" ]; then
FINDINGS="${FINDINGS}
### ⚠️ WARNING: base64 encoding/decoding detected
Base64 has legitimate uses (images, JWT, etc.) but is also commonly used to obfuscate malicious payloads. Verify the usage is appropriate.
**Matches (first 20):**
\`\`\`
${B64_HITS}
\`\`\`
"
fi
# --- exec/eval with string arguments ---
EXEC_HITS=$(echo "$DIFF" | grep -n '^\+' | grep -E '(exec|eval)\s*\(' | grep -v '^\+\s*#' | grep -v 'test_\|mock\|assert\|# ' | head -20 || true)
if [ -n "$EXEC_HITS" ]; then
FINDINGS="${FINDINGS}
### ⚠️ WARNING: exec() or eval() usage
Dynamic code execution can hide malicious behavior, especially when combined with base64 or network fetches.
**Matches (first 20):**
\`\`\`
${EXEC_HITS}
\`\`\`
"
fi
# --- subprocess with encoded/obfuscated commands ---
PROC_HITS=$(echo "$DIFF" | grep -n '^\+' | grep -E 'subprocess\.(Popen|call|run)\s*\(' | grep -iE 'base64|decode|encode|\\x|chr\(' | head -10 || true)
if [ -n "$PROC_HITS" ]; then
CRITICAL=true
FINDINGS="${FINDINGS}
### 🚨 CRITICAL: subprocess with encoded/obfuscated command
Subprocess calls with encoded arguments are a strong indicator of payload execution.
**Matches:**
\`\`\`
${PROC_HITS}
\`\`\`
"
fi
# --- Network calls to non-standard domains ---
EXFIL_HITS=$(echo "$DIFF" | grep -n '^\+' | grep -iE 'requests\.(post|put)\(|httpx\.(post|put)\(|urllib\.request\.urlopen' | grep -v '^\+\s*#' | grep -v 'test_\|mock\|assert' | head -10 || true)
if [ -n "$EXFIL_HITS" ]; then
FINDINGS="${FINDINGS}
### ⚠️ WARNING: Outbound network calls (POST/PUT)
Outbound POST/PUT requests in new code could be data exfiltration. Verify the destination URLs are legitimate.
**Matches (first 10):**
\`\`\`
${EXFIL_HITS}
\`\`\`
"
fi
# --- setup.py / setup.cfg install hooks ---
SETUP_HITS=$(git diff --name-only "$BASE".."$HEAD" | grep -E '(setup\.py|setup\.cfg|__init__\.pth|sitecustomize\.py|usercustomize\.py)$' || true)
if [ -n "$SETUP_HITS" ]; then
FINDINGS="${FINDINGS}
### ⚠️ WARNING: Install hook files modified
These files can execute code during package installation or interpreter startup.
**Files:**
\`\`\`
${SETUP_HITS}
\`\`\`
"
fi
# --- Compile/marshal/pickle (code object injection) ---
MARSHAL_HITS=$(echo "$DIFF" | grep -n '^\+' | grep -iE 'marshal\.loads|pickle\.loads|compile\(' | grep -v '^\+\s*#' | grep -v 'test_\|re\.compile\|ast\.compile' | head -10 || true)
if [ -n "$MARSHAL_HITS" ]; then
FINDINGS="${FINDINGS}
### ⚠️ WARNING: marshal/pickle/compile usage
These can deserialize or construct executable code objects.
**Matches:**
\`\`\`
${MARSHAL_HITS}
\`\`\`
"
fi
# --- Output results ---
if [ -n "$FINDINGS" ]; then
echo "found=true" >> "$GITHUB_OUTPUT"
if [ "$CRITICAL" = true ]; then
echo "critical=true" >> "$GITHUB_OUTPUT"
else
echo "critical=false" >> "$GITHUB_OUTPUT"
fi
# Write findings to a file (multiline env vars are fragile)
echo "$FINDINGS" > /tmp/findings.md
else
echo "found=false" >> "$GITHUB_OUTPUT"
echo "critical=false" >> "$GITHUB_OUTPUT"
fi
- name: Post warning comment
if: steps.scan.outputs.found == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
SEVERITY="⚠️ Supply Chain Risk Detected"
if [ "${{ steps.scan.outputs.critical }}" = "true" ]; then
SEVERITY="🚨 CRITICAL Supply Chain Risk Detected"
fi
BODY="## ${SEVERITY}
This PR contains patterns commonly associated with supply chain attacks. This does **not** mean the PR is malicious — but these patterns require careful human review before merging.
$(cat /tmp/findings.md)
---
*Automated scan triggered by [supply-chain-audit](/.github/workflows/supply-chain-audit.yml). If this is a false positive, a maintainer can approve after manual review.*"
gh pr comment "${{ github.event.pull_request.number }}" --body "$BODY"
- name: Fail on critical findings
if: steps.scan.outputs.critical == 'true'
run: |
echo "::error::CRITICAL supply chain risk patterns detected in this PR. See the PR comment for details."
exit 1

1
.gitignore vendored
View file

@ -11,6 +11,7 @@ __pycache__/
.env.production.local
.env.development
.env.test
docker-compose.override.yml
export*
__pycache__/model_tools.cpython-310.pyc
__pycache__/web_tools.cpython-310.pyc

21
Dockerfile Normal file
View file

@ -0,0 +1,21 @@
FROM nikolaik/python-nodejs:python3.11-nodejs20
RUN apt-get update && apt-get install -y \
curl \
git \
docker.io \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/hermes_code /app/hermes_data
WORKDIR /app/hermes_code
COPY pyproject.toml requirements.txt* ./
RUN pip install --no-cache-dir browser-use playwright python-telegram-bot
COPY . .
RUN pip install -e .
CMD ["python", "-m", "gateway.run"]

64
docker-compose.yml Normal file
View file

@ -0,0 +1,64 @@
services:
browser:
image: browserless/chrome:latest
container_name: hermes_browser
ports:
- "3000:3000"
environment:
- MAX_CONCURRENT_SESSIONS=5
- SCREEN_WIDTH=1280
- SCREEN_HEIGHT=720
- ENABLE_DEBUGGER=true
restart: always
networks:
- hermes-net
deploy:
resources:
limits:
memory: 2G
agent:
build: .
container_name: hermes-brain
env_file:
- .env
volumes:
- .:/app/hermes_code:ro
- ${HERMES_DATA_PATH}/config.yaml:/app/hermes_data/config.yaml:ro
- ${HERMES_DATA_PATH}/SOUL.md:/app/hermes_data/SOUL.md:ro
- ./.env:/app/hermes_data/.env:ro
- ${HERMES_DATA_PATH}/state.db:/app/hermes_data/state.db:rw
- ${HERMES_DATA_PATH}/sessions:/app/hermes_data/sessions:rw
- ${HERMES_DATA_PATH}/logs:/app/hermes_data/logs:rw
- ${HERMES_DATA_PATH}/skills:/app/hermes_data/skills:rw
- ${HERMES_DATA_PATH}/sandboxes:/app/hermes_data/sandboxes:rw
- ${HERMES_DATA_PATH}/memories:/app/hermes_data/memories:rw
- ${HERMES_WORKSPACE_PATH}/hermes:/app/hermes_data/workspace:rw
environment:
- BROWSER_URL=ws://browser:3000
depends_on:
- browser
stdin_open: true
tty: true
restart: always
networks:
- hermes-net
deploy:
resources:
limits:
memory: 1.5G
networks:
hermes-net:
driver: bridge
# Optional: SSL configuration if needed
# extra_hosts:
# - "host.docker.internal:host-gateway"
# Uncomment the following if you need persistent logging or data
# volumes:
# - ./logs:/tmp/logs

View file

@ -64,7 +64,7 @@ rl = [
"uvicorn[standard]>=0.24.0,<1",
"wandb>=0.15.0,<1",
]
yc-bench = ["yc-bench @ git+https://github.com/collinear-ai/yc-bench.git"]
yc-bench = ["yc-bench @ git+https://github.com/collinear-ai/yc-bench.git ; python_version >= '3.12'"]
all = [
"hermes-agent[modal]",
"hermes-agent[daytona]",

View file

@ -4609,9 +4609,18 @@ class AIAgent:
except Exception as e:
logger.debug("Session DB compression split failed: %s", e)
# Reset context pressure warnings — usage drops after compaction
# Reset context pressure warnings and token estimate — usage drops
# after compaction. Without this, the stale last_prompt_tokens from
# the previous API call causes the pressure calculation to stay at
# >1000% and spam warnings / re-trigger compression in a loop.
self._context_50_warned = False
self._context_70_warned = False
_compressed_est = (
estimate_tokens_rough(new_system_prompt)
+ estimate_messages_tokens_rough(compressed)
)
self.context_compressor.last_prompt_tokens = _compressed_est
self.context_compressor.last_completion_tokens = 0
return compressed, new_system_prompt

View file

@ -116,9 +116,20 @@ export VIRTUAL_ENV="$SCRIPT_DIR/venv"
echo -e "${CYAN}${NC} Installing dependencies..."
$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "."
echo -e "${GREEN}${NC} Dependencies installed"
# Prefer uv sync with lockfile (hash-verified installs) when available,
# fall back to pip install for compatibility or when lockfile is stale.
if [ -f "uv.lock" ]; then
echo -e "${CYAN}${NC} Using uv.lock for hash-verified installation..."
UV_PROJECT_ENVIRONMENT="$SCRIPT_DIR/venv" $UV_CMD sync --all-extras --locked 2>/dev/null && \
echo -e "${GREEN}${NC} Dependencies installed (lockfile verified)" || {
echo -e "${YELLOW}${NC} Lockfile install failed (may be outdated), falling back to pip install..."
$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "."
echo -e "${GREEN}${NC} Dependencies installed"
}
else
$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "."
echo -e "${GREEN}${NC} Dependencies installed"
fi
# ============================================================================
# Submodules (terminal backend + RL training)

2945
uv.lock generated

File diff suppressed because it is too large Load diff

View file

@ -57,6 +57,15 @@ 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
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
@ -91,6 +100,57 @@ platforms: [windows] # Windows only
When set, the skill is automatically hidden from the system prompt, `skills_list()`, and slash commands on incompatible platforms. If omitted or empty, the skill loads on all platforms (backward compatible).
### Conditional Skill Activation
Skills can declare dependencies on specific tools or toolsets. This controls whether the skill appears in the system prompt for a given session.
```yaml
metadata:
hermes:
requires_toolsets: [web] # Hide if the web toolset is NOT active
requires_tools: [web_search] # Hide if web_search tool is NOT available
fallback_for_toolsets: [browser] # Hide if the browser toolset IS active
fallback_for_tools: [browser_navigate] # Hide if browser_navigate IS available
```
| Field | Behavior |
|-------|----------|
| `requires_toolsets` | Skill is **hidden** when ANY listed toolset is **not** available |
| `requires_tools` | Skill is **hidden** when ANY listed tool is **not** available |
| `fallback_for_toolsets` | Skill is **hidden** when ANY listed toolset **is** available |
| `fallback_for_tools` | Skill is **hidden** when ANY listed tool **is** available |
**Use case for `fallback_for_*`:** Create a skill that serves as a workaround when a primary tool isn't available. For example, a `duckduckgo-search` skill with `fallback_for_tools: [web_search]` only shows when the web search tool (which requires an API key) is not configured.
**Use case for `requires_*`:** Create a skill that only makes sense when certain tools are present. For example, a web scraping workflow skill with `requires_toolsets: [web]` won't clutter the prompt when web tools are disabled.
### Environment Variable Requirements
Skills can declare environment variables they need. When a skill is loaded via `skill_view`, its required vars are automatically registered for passthrough into sandboxed execution environments (terminal, execute_code).
```yaml
required_environment_variables:
- name: TENOR_API_KEY
prompt: "Tenor API key" # Shown when prompting user
help: "Get your key at https://tenor.com" # Help text or URL
required_for: "GIF search functionality" # What needs this var
```
Each entry supports:
- `name` (required) — the environment variable name
- `prompt` (optional) — prompt text when asking the user for the value
- `help` (optional) — help text or URL for obtaining the value
- `required_for` (optional) — describes which feature needs this variable
Users can also manually configure passthrough variables in `config.yaml`:
```yaml
terminal:
env_passthrough:
- MY_CUSTOM_VAR
- ANOTHER_VAR
```
See `skills/apple/` for examples of macOS-only skills.
## Secure Setup on Load

View file

@ -139,7 +139,7 @@ hermes gateway setup # Interactive platform configuration
Want microphone input in the CLI or spoken replies in messaging?
```bash
pip install hermes-agent[voice]
pip install "hermes-agent[voice]"
# Optional but recommended for free local speech-to-text
pip install faster-whisper

View file

@ -57,19 +57,19 @@ If that is not solid yet, fix text mode first.
### CLI microphone + playback
```bash
pip install hermes-agent[voice]
pip install "hermes-agent[voice]"
```
### Messaging platforms
```bash
pip install hermes-agent[messaging]
pip install "hermes-agent[messaging]"
```
### Premium ElevenLabs TTS
```bash
pip install hermes-agent[tts-premium]
pip install "hermes-agent[tts-premium]"
```
### Local NeuTTS (optional)
@ -81,7 +81,7 @@ python -m pip install -U neutts[all]
### Everything
```bash
pip install hermes-agent[all]
pip install "hermes-agent[all]"
```
## Step 3: install system dependencies

View file

@ -348,7 +348,7 @@ Configure in `~/.hermes/config.yaml` under your gateway's settings. See the [Mes
**Solution:**
```bash
# Install messaging dependencies
pip install hermes-agent[telegram] # or [discord], [slack], [whatsapp]
pip install "hermes-agent[telegram]" # or [discord], [slack], [whatsapp]
# Check for port conflicts
lsof -i :8080

View file

@ -55,6 +55,22 @@ Settings are resolved in this order (highest priority first):
Secrets (API keys, bot tokens, passwords) go in `.env`. Everything else (model, terminal backend, compression settings, memory limits, toolsets) goes in `config.yaml`. When both are set, `config.yaml` wins for non-secret settings.
:::
## Environment Variable Substitution
You can reference environment variables in `config.yaml` using `${VAR_NAME}` syntax:
```yaml
auxiliary:
vision:
api_key: ${GOOGLE_API_KEY}
base_url: ${CUSTOM_VISION_URL}
delegation:
api_key: ${DELEGATION_KEY}
```
Multiple references in a single value work: `url: "${HOST}:${PORT}"`. If a referenced variable is not set, the placeholder is kept verbatim (`${UNDEFINED_VAR}` stays as-is). Only the `${VAR}` syntax is supported — bare `$VAR` is not expanded.
## Inference Providers
You need at least one way to connect to an LLM. Use `hermes model` to switch providers and models interactively, or configure directly:
@ -320,7 +336,7 @@ vLLM supports tool calling, structured output, and multi-modal models. Use `--en
```bash
# Start SGLang server
pip install sglang[all]
pip install "sglang[all]"
python -m sglang.launch_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--port 8000 \
@ -363,7 +379,7 @@ Download GGUF models from [Hugging Face](https://huggingface.co/models?library=g
```bash
# Install and start
pip install litellm[proxy]
pip install "litellm[proxy]"
litellm --model anthropic/claude-sonnet-4 --port 4000
# Or with a config file for multiple models:
@ -1329,6 +1345,23 @@ Usage: type `/status`, `/disk`, `/update`, or `/gpu` in the CLI or any messaging
- **Type** — only `exec` is supported (runs a shell command); other types show an error
- **Works everywhere** — CLI, Telegram, Discord, Slack, WhatsApp, Signal, Email, Home Assistant
## Gateway Streaming
Enable progressive token delivery on messaging platforms. When streaming is enabled, responses appear character-by-character in Telegram, Discord, and Slack via message editing, rather than waiting for the full response.
```yaml
streaming:
enabled: false # Enable streaming token delivery (default: off)
transport: edit # "edit" (progressive message editing) or "off"
edit_interval: 0.3 # Min seconds between message edits
buffer_threshold: 40 # Characters accumulated before forcing an edit
cursor: " ▉" # Cursor character shown during streaming
```
**Platform support:** Telegram, Discord, and Slack support edit-based streaming. Platforms that don't support message editing (Signal, Email, Home Assistant) are auto-detected on the first attempt — streaming is gracefully disabled for that session with no flood of messages.
**Overflow handling:** If the streamed text exceeds the platform's message length limit (~4096 chars), the current message is finalized and a new one starts automatically.
## Human Delay
Simulate human-like response pacing in messaging platforms:
@ -1350,6 +1383,27 @@ code_execution:
max_tool_calls: 50 # Max tool calls within code execution
```
## Web Search Backends
The `web_search`, `web_extract`, and `web_crawl` tools support three backend providers. Configure the backend in `config.yaml` or via `hermes tools`:
```yaml
web:
backend: firecrawl # firecrawl | parallel | tavily
```
| Backend | Env Var | Search | Extract | Crawl |
|---------|---------|--------|---------|-------|
| **Firecrawl** (default) | `FIRECRAWL_API_KEY` | ✔ | ✔ | ✔ |
| **Parallel** | `PARALLEL_API_KEY` | ✔ | ✔ | — |
| **Tavily** | `TAVILY_API_KEY` | ✔ | ✔ | ✔ |
**Backend selection:** If `web.backend` is not set, the backend is auto-detected from available API keys. If only `TAVILY_API_KEY` is set, Tavily is used. If only `PARALLEL_API_KEY` is set, Parallel is used. Otherwise Firecrawl is the default.
**Self-hosted Firecrawl:** Set `FIRECRAWL_API_URL` to point at your own instance. When a custom URL is set, the API key becomes optional (set `USE_DB_AUTHENTICATION=false` on the server to disable auth).
**Parallel search modes:** Set `PARALLEL_SEARCH_MODE` to control search behavior — `fast`, `one-shot`, or `agentic` (default: `agentic`).
## Browser
Configure browser automation behavior:

View file

@ -231,6 +231,6 @@ Any frontend that supports the OpenAI API format works. Tested/documented integr
## Limitations
- **Response storage is in-memory** — stored responses (for `previous_response_id`) are lost on gateway restart. Max 100 stored responses (LRU eviction).
- **Response storage** — stored responses (for `previous_response_id`) are persisted in SQLite and survive gateway restarts. Max 100 stored responses (LRU eviction).
- **No file upload** — vision/document analysis via uploaded files is not yet supported through the API.
- **Model field is cosmetic** — the `model` field in requests is accepted but the actual LLM model used is configured server-side in config.yaml.

View file

@ -0,0 +1,109 @@
---
sidebar_position: 9
title: "Context References"
description: "Inline @-syntax for attaching files, folders, git diffs, and URLs directly into your messages"
---
# Context References
Type `@` followed by a reference to inject content directly into your message. Hermes expands the reference inline and appends the content under an `--- Attached Context ---` section.
## Supported References
| Syntax | Description |
|--------|-------------|
| `@file:path/to/file.py` | Inject file contents |
| `@file:path/to/file.py:10-25` | Inject specific line range (1-indexed, inclusive) |
| `@folder:path/to/dir` | Inject directory tree listing with file metadata |
| `@diff` | Inject `git diff` (unstaged working tree changes) |
| `@staged` | Inject `git diff --staged` (staged changes) |
| `@git:5` | Inject last N commits with patches (max 10) |
| `@url:https://example.com` | Fetch and inject web page content |
## Usage Examples
```text
Review @file:src/main.py and suggest improvements
What changed? @diff
Compare @file:old_config.yaml and @file:new_config.yaml
What's in @folder:src/components?
Summarize this article @url:https://arxiv.org/abs/2301.00001
```
Multiple references work in a single message:
```text
Check @file:main.py, and also @file:test.py.
```
Trailing punctuation (`,`, `.`, `;`, `!`, `?`) is automatically stripped from reference values.
## CLI Tab Completion
In the interactive CLI, typing `@` triggers autocomplete:
- `@` shows all reference types (`@diff`, `@staged`, `@file:`, `@folder:`, `@git:`, `@url:`)
- `@file:` and `@folder:` trigger filesystem path completion with file size metadata
- Bare `@` followed by partial text shows matching files and folders from the current directory
## Line Ranges
The `@file:` reference supports line ranges for precise content injection:
```text
@file:src/main.py:42 # Single line 42
@file:src/main.py:10-25 # Lines 10 through 25 (inclusive)
```
Lines are 1-indexed. Invalid ranges are silently ignored (full file is returned).
## Size Limits
Context references are bounded to prevent overwhelming the model's context window:
| Threshold | Value | Behavior |
|-----------|-------|----------|
| Soft limit | 25% of context length | Warning appended, expansion proceeds |
| Hard limit | 50% of context length | Expansion refused, original message returned unchanged |
| Folder entries | 200 files max | Excess entries replaced with `- ...` |
| Git commits | 10 max | `@git:N` clamped to range [1, 10] |
## Security
### Sensitive Path Blocking
These paths are always blocked from `@file:` references to prevent credential exposure:
- SSH keys and config: `~/.ssh/id_rsa`, `~/.ssh/id_ed25519`, `~/.ssh/authorized_keys`, `~/.ssh/config`
- Shell profiles: `~/.bashrc`, `~/.zshrc`, `~/.profile`, `~/.bash_profile`, `~/.zprofile`
- Credential files: `~/.netrc`, `~/.pgpass`, `~/.npmrc`, `~/.pypirc`
- Hermes env: `$HERMES_HOME/.env`
These directories are fully blocked (any file inside):
- `~/.ssh/`, `~/.aws/`, `~/.gnupg/`, `~/.kube/`, `$HERMES_HOME/skills/.hub/`
### Path Traversal Protection
All paths are resolved relative to the working directory. References that resolve outside the allowed workspace root are rejected.
### Binary File Detection
Binary files are detected via MIME type and null-byte scanning. Known text extensions (`.py`, `.md`, `.json`, `.yaml`, `.toml`, `.js`, `.ts`, etc.) bypass MIME-based detection. Binary files are rejected with a warning.
## Error Handling
Invalid references produce inline warnings rather than failures:
| Condition | Behavior |
|-----------|----------|
| File not found | Warning: "file not found" |
| Binary file | Warning: "binary files are not supported" |
| Folder not found | Warning: "folder not found" |
| Git command fails | Warning with git stderr |
| URL returns no content | Warning: "no content extracted" |
| Sensitive path | Warning: "path is a sensitive credential file" |
| Path outside workspace | Warning: "path is outside the allowed workspace" |

View file

@ -36,19 +36,19 @@ The `~/.hermes/` directory and default `config.yaml` are created automatically t
```bash
# CLI voice mode (microphone + audio playback)
pip install hermes-agent[voice]
pip install "hermes-agent[voice]"
# Discord + Telegram messaging (includes discord.py[voice] for VC support)
pip install hermes-agent[messaging]
pip install "hermes-agent[messaging]"
# Premium TTS (ElevenLabs)
pip install hermes-agent[tts-premium]
pip install "hermes-agent[tts-premium]"
# Local TTS (NeuTTS, optional)
python -m pip install -U neutts[all]
# Everything at once
pip install hermes-agent[all]
pip install "hermes-agent[all]"
```
| Extra | Packages | Required For |

View file

@ -358,6 +358,42 @@ When a blocked URL is requested, the tool returns an error explaining the domain
See [Website Blocklist](/docs/user-guide/configuration#website-blocklist) in the configuration guide for full details.
### SSRF Protection
All URL-capable tools (web search, web extract, vision, browser) validate URLs before fetching them to prevent Server-Side Request Forgery (SSRF) attacks. Blocked addresses include:
- **Private networks** (RFC 1918): `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`
- **Loopback**: `127.0.0.0/8`, `::1`
- **Link-local**: `169.254.0.0/16` (includes cloud metadata at `169.254.169.254`)
- **CGNAT / shared address space** (RFC 6598): `100.64.0.0/10` (Tailscale, WireGuard VPNs)
- **Cloud metadata hostnames**: `metadata.google.internal`, `metadata.goog`
- **Reserved, multicast, and unspecified addresses**
SSRF protection is always active and cannot be disabled. DNS failures are treated as blocked (fail-closed). Redirect chains are re-validated at each hop to prevent redirect-based bypasses.
### Tirith Pre-Exec Security Scanning
Hermes integrates [tirith](https://github.com/sheeki03/tirith) for content-level command scanning before execution. Tirith detects threats that pattern matching alone misses:
- Homograph URL spoofing (internationalized domain attacks)
- Pipe-to-interpreter patterns (`curl | bash`, `wget | sh`)
- Terminal injection attacks
Tirith auto-installs from GitHub releases on first use with SHA-256 checksum verification (and cosign provenance verification if cosign is available).
```yaml
# In ~/.hermes/config.yaml
security:
tirith_enabled: true # Enable/disable tirith scanning (default: true)
tirith_path: "tirith" # Path to tirith binary (default: PATH lookup)
tirith_timeout: 5 # Subprocess timeout in seconds
tirith_fail_open: true # Allow execution when tirith is unavailable (default: true)
```
When `tirith_fail_open` is `true` (default), commands proceed if tirith is not installed or times out. Set to `false` in high-security environments to block commands when tirith is unavailable.
Tirith's verdict integrates with the approval flow: safe commands pass through, suspicious commands trigger user approval, and dangerous commands are blocked.
### Context File Injection Protection
Context files (AGENTS.md, .cursorrules, SOUL.md) are scanned for prompt injection before being included in the system prompt. The scanner checks for:

View file

@ -114,7 +114,13 @@ Session IDs follow the format `YYYYMMDD_HHMMSS_<8-char-hex>`, e.g. `20250305_091
Give sessions human-readable titles so you can find and resume them easily.
### Setting a Title
### Auto-Generated Titles
Hermes automatically generates a short descriptive title (37 words) for each session after the first exchange. This runs in a background thread using a fast auxiliary model, so it adds no latency. You'll see auto-generated titles when browsing sessions with `hermes sessions list` or `hermes sessions browse`.
Auto-titling only fires once per session and is skipped if you've already set a title manually.
### Setting a Title Manually
Use the `/title` slash command inside any chat session (CLI or gateway):