mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Merge branch 'main' of github.com:NousResearch/hermes-agent into feat/ink-refactor
This commit is contained in:
commit
842a122964
11 changed files with 109 additions and 50 deletions
27
RELEASE_v0.10.0.md
Normal file
27
RELEASE_v0.10.0.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Hermes Agent v0.10.0 (v2026.4.16)
|
||||||
|
|
||||||
|
**Release Date:** April 16, 2026
|
||||||
|
|
||||||
|
> The Tool Gateway release — paid Nous Portal subscribers can now use web search, image generation, text-to-speech, and browser automation through their existing subscription with zero additional API keys.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Highlights
|
||||||
|
|
||||||
|
- **Nous Tool Gateway** — Paid [Nous Portal](https://portal.nousresearch.com) subscribers now get automatic access to **web search** (Firecrawl), **image generation** (FAL / FLUX 2 Pro), **text-to-speech** (OpenAI TTS), and **browser automation** (Browser Use) through their existing subscription. No separate API keys needed — just run `hermes model`, select Nous Portal, and pick which tools to enable. Per-tool opt-in via `use_gateway` config, full integration with `hermes tools` and `hermes status`, and the runtime correctly prefers the gateway even when direct API keys exist. Replaces the old hidden `HERMES_ENABLE_NOUS_MANAGED_TOOLS` env var with clean subscription-based detection. ([#11206](https://github.com/NousResearch/hermes-agent/pull/11206), based on work by @jquesnelle; docs: [#11208](https://github.com/NousResearch/hermes-agent/pull/11208))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Bug Fixes & Improvements
|
||||||
|
|
||||||
|
This release includes 180+ commits with numerous bug fixes, platform improvements, and reliability enhancements across the agent core, gateway, CLI, and tool system. Full details will be published in the v0.11.0 changelog.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 Contributors
|
||||||
|
|
||||||
|
- **@jquesnelle** (emozilla) — Original Tool Gateway implementation ([#10799](https://github.com/NousResearch/hermes-agent/pull/10799)), salvaged and shipped in this release
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Full Changelog**: [v2026.4.13...v2026.4.16](https://github.com/NousResearch/hermes-agent/compare/v2026.4.13...v2026.4.16)
|
||||||
|
|
@ -1598,11 +1598,21 @@ class MatrixAdapter(BasePlatformAdapter):
|
||||||
if not self._client:
|
if not self._client:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
await self._client.set_read_markers(
|
room = RoomID(room_id)
|
||||||
RoomID(room_id),
|
event = EventID(event_id)
|
||||||
fully_read_event=EventID(event_id),
|
if hasattr(self._client, "set_fully_read_marker"):
|
||||||
read_receipt=EventID(event_id),
|
await self._client.set_fully_read_marker(room, event, event)
|
||||||
)
|
elif hasattr(self._client, "send_receipt"):
|
||||||
|
await self._client.send_receipt(room, event)
|
||||||
|
elif hasattr(self._client, "set_read_markers"):
|
||||||
|
await self._client.set_read_markers(
|
||||||
|
room,
|
||||||
|
fully_read_event=event,
|
||||||
|
read_receipt=event,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug("Matrix: client has no read receipt method")
|
||||||
|
return False
|
||||||
logger.debug("Matrix: sent read receipt for %s in %s", event_id, room_id)
|
logger.debug("Matrix: sent read receipt for %s in %s", event_id, room_id)
|
||||||
return True
|
return True
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ Provides subcommands for:
|
||||||
- hermes cron - Manage cron jobs
|
- hermes cron - Manage cron jobs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.9.0"
|
__version__ = "0.10.0"
|
||||||
__release_date__ = "2026.4.13"
|
__release_date__ = "2026.4.16"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "hermes-agent"
|
name = "hermes-agent"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
description = "The self-improving AI agent — creates skills from experience, improves them during use, and runs anywhere"
|
description = "The self-improving AI agent — creates skills from experience, improves them during use, and runs anywhere"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ AUTHOR_MAP = {
|
||||||
"241404605+MestreY0d4-Uninter@users.noreply.github.com": "MestreY0d4-Uninter",
|
"241404605+MestreY0d4-Uninter@users.noreply.github.com": "MestreY0d4-Uninter",
|
||||||
"109555139+davetist@users.noreply.github.com": "davetist",
|
"109555139+davetist@users.noreply.github.com": "davetist",
|
||||||
# contributors (manual mapping from git names)
|
# contributors (manual mapping from git names)
|
||||||
|
"ahmedsherif95@gmail.com": "asheriif",
|
||||||
"dmayhem93@gmail.com": "dmahan93",
|
"dmayhem93@gmail.com": "dmahan93",
|
||||||
"samherring99@gmail.com": "samherring99",
|
"samherring99@gmail.com": "samherring99",
|
||||||
"desaiaum08@gmail.com": "Aum08Desai",
|
"desaiaum08@gmail.com": "Aum08Desai",
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,6 @@ metadata:
|
||||||
tags: [wiki, knowledge-base, research, notes, markdown, rag-alternative]
|
tags: [wiki, knowledge-base, research, notes, markdown, rag-alternative]
|
||||||
category: research
|
category: research
|
||||||
related_skills: [obsidian, arxiv, agentic-research-ideas]
|
related_skills: [obsidian, arxiv, agentic-research-ideas]
|
||||||
config:
|
|
||||||
- key: wiki.path
|
|
||||||
description: Path to the LLM Wiki knowledge base directory
|
|
||||||
default: "~/wiki"
|
|
||||||
prompt: Wiki directory path
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Karpathy's LLM Wiki
|
# Karpathy's LLM Wiki
|
||||||
|
|
@ -39,19 +34,14 @@ Use this skill when the user:
|
||||||
|
|
||||||
## Wiki Location
|
## Wiki Location
|
||||||
|
|
||||||
Configured via `skills.config.wiki.path` in `~/.hermes/config.yaml` (prompted
|
**Location:** Set via `WIKI_PATH` environment variable (e.g. in `~/.hermes/.env`).
|
||||||
during `hermes config migrate` or `hermes setup`):
|
|
||||||
|
|
||||||
```yaml
|
If unset, defaults to `~/wiki`.
|
||||||
skills:
|
|
||||||
config:
|
```bash
|
||||||
wiki:
|
WIKI="${WIKI_PATH:-$HOME/wiki}"
|
||||||
path: ~/wiki
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Falls back to `~/wiki` default. The resolved path is injected when this
|
|
||||||
skill loads — check the `[Skill config: ...]` block above for the active value.
|
|
||||||
|
|
||||||
The wiki is just a directory of markdown files — open it in Obsidian, VS Code, or
|
The wiki is just a directory of markdown files — open it in Obsidian, VS Code, or
|
||||||
any editor. No database, no special tooling required.
|
any editor. No database, no special tooling required.
|
||||||
|
|
||||||
|
|
@ -87,7 +77,7 @@ When the user has an existing wiki, **always orient yourself before doing anythi
|
||||||
③ **Scan recent `log.md`** — read the last 20-30 entries to understand recent activity.
|
③ **Scan recent `log.md`** — read the last 20-30 entries to understand recent activity.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
WIKI="${wiki_path:-$HOME/wiki}"
|
WIKI="${WIKI_PATH:-$HOME/wiki}"
|
||||||
# Orientation reads at session start
|
# Orientation reads at session start
|
||||||
read_file "$WIKI/SCHEMA.md"
|
read_file "$WIKI/SCHEMA.md"
|
||||||
read_file "$WIKI/index.md"
|
read_file "$WIKI/index.md"
|
||||||
|
|
@ -107,7 +97,7 @@ at hand before creating anything new.
|
||||||
|
|
||||||
When the user asks to create or start a wiki:
|
When the user asks to create or start a wiki:
|
||||||
|
|
||||||
1. Determine the wiki path (from config, env var, or ask the user; default `~/wiki`)
|
1. Determine the wiki path (from `$WIKI_PATH` env var, or ask the user; default `~/wiki`)
|
||||||
2. Create the directory structure above
|
2. Create the directory structure above
|
||||||
3. Ask the user what domain the wiki covers — be specific
|
3. Ask the user what domain the wiki covers — be specific
|
||||||
4. Write `SCHEMA.md` customized to the domain (see template below)
|
4. Write `SCHEMA.md` customized to the domain (see template below)
|
||||||
|
|
|
||||||
|
|
@ -1740,16 +1740,49 @@ class TestMatrixReadReceipts:
|
||||||
def setup_method(self):
|
def setup_method(self):
|
||||||
self.adapter = _make_adapter()
|
self.adapter = _make_adapter()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_accepted_message_schedules_read_receipt(self):
|
||||||
|
self.adapter._is_dm_room = AsyncMock(return_value=True)
|
||||||
|
self.adapter._get_display_name = AsyncMock(return_value="Alice")
|
||||||
|
self.adapter._background_read_receipt = MagicMock()
|
||||||
|
|
||||||
|
ctx = await self.adapter._resolve_message_context(
|
||||||
|
room_id="!room:ex",
|
||||||
|
sender="@alice:ex",
|
||||||
|
event_id="$event1",
|
||||||
|
body="hello",
|
||||||
|
source_content={"body": "hello"},
|
||||||
|
relates_to={},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert ctx is not None
|
||||||
|
self.adapter._background_read_receipt.assert_called_once_with(
|
||||||
|
"!room:ex", "$event1"
|
||||||
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_send_read_receipt(self):
|
async def test_send_read_receipt(self):
|
||||||
"""send_read_receipt should call client.set_read_markers."""
|
"""send_read_receipt should call mautrix's real read-marker API."""
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client.set_read_markers = AsyncMock(return_value=None)
|
mock_client.set_fully_read_marker = AsyncMock(return_value=None)
|
||||||
self.adapter._client = mock_client
|
self.adapter._client = mock_client
|
||||||
|
|
||||||
result = await self.adapter.send_read_receipt("!room:ex", "$event1")
|
result = await self.adapter.send_read_receipt("!room:ex", "$event1")
|
||||||
assert result is True
|
assert result is True
|
||||||
mock_client.set_read_markers.assert_called_once()
|
mock_client.set_fully_read_marker.assert_awaited_once_with(
|
||||||
|
"!room:ex", "$event1", "$event1"
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_send_read_receipt_falls_back_to_receipt_only(self):
|
||||||
|
"""send_read_receipt should still work with clients lacking read markers."""
|
||||||
|
mock_client = MagicMock(spec=["send_receipt"])
|
||||||
|
mock_client.send_receipt = AsyncMock(return_value=None)
|
||||||
|
self.adapter._client = mock_client
|
||||||
|
|
||||||
|
result = await self.adapter.send_read_receipt("!room:ex", "$event1")
|
||||||
|
assert result is True
|
||||||
|
mock_client.send_receipt.assert_awaited_once_with("!room:ex", "$event1")
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_read_receipt_no_client(self):
|
async def test_read_receipt_no_client(self):
|
||||||
|
|
@ -1852,5 +1885,3 @@ class TestMatrixPresence:
|
||||||
self.adapter._client = None
|
self.adapter._client = None
|
||||||
result = await self.adapter.set_presence("online")
|
result = await self.adapter.set_presence("online")
|
||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,18 +186,18 @@ Skills can declare non-secret settings that are stored in `config.yaml` under th
|
||||||
metadata:
|
metadata:
|
||||||
hermes:
|
hermes:
|
||||||
config:
|
config:
|
||||||
- key: wiki.path
|
- key: myplugin.path
|
||||||
description: Path to the LLM Wiki knowledge base directory
|
description: Path to the plugin data directory
|
||||||
default: "~/wiki"
|
default: "~/myplugin-data"
|
||||||
prompt: Wiki directory path
|
prompt: Plugin data directory path
|
||||||
- key: wiki.domain
|
- key: myplugin.domain
|
||||||
description: Domain the wiki covers
|
description: Domain the plugin operates on
|
||||||
default: ""
|
default: ""
|
||||||
prompt: Wiki domain (e.g., AI/ML research)
|
prompt: Plugin domain (e.g., AI/ML research)
|
||||||
```
|
```
|
||||||
|
|
||||||
Each entry supports:
|
Each entry supports:
|
||||||
- `key` (required) — dotpath for the setting (e.g., `wiki.path`)
|
- `key` (required) — dotpath for the setting (e.g., `myplugin.path`)
|
||||||
- `description` (required) — explains what the setting controls
|
- `description` (required) — explains what the setting controls
|
||||||
- `default` (optional) — default value if the user doesn't configure it
|
- `default` (optional) — default value if the user doesn't configure it
|
||||||
- `prompt` (optional) — prompt text shown during `hermes config migrate`; falls back to `description`
|
- `prompt` (optional) — prompt text shown during `hermes config migrate`; falls back to `description`
|
||||||
|
|
@ -208,8 +208,8 @@ Each entry supports:
|
||||||
```yaml
|
```yaml
|
||||||
skills:
|
skills:
|
||||||
config:
|
config:
|
||||||
wiki:
|
myplugin:
|
||||||
path: ~/my-research
|
path: ~/my-data
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Discovery:** `hermes config migrate` scans all enabled skills, finds unconfigured settings, and prompts the user. Settings also appear in `hermes config show` under "Skill Settings."
|
2. **Discovery:** `hermes config migrate` scans all enabled skills, finds unconfigured settings, and prompts the user. Settings also appear in `hermes config show` under "Skill Settings."
|
||||||
|
|
@ -217,14 +217,14 @@ Each entry supports:
|
||||||
3. **Runtime injection:** When a skill loads, its config values are resolved and appended to the skill message:
|
3. **Runtime injection:** When a skill loads, its config values are resolved and appended to the skill message:
|
||||||
```
|
```
|
||||||
[Skill config (from ~/.hermes/config.yaml):
|
[Skill config (from ~/.hermes/config.yaml):
|
||||||
wiki.path = /home/user/my-research
|
myplugin.path = /home/user/my-data
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
The agent sees the configured values without needing to read `config.yaml` itself.
|
The agent sees the configured values without needing to read `config.yaml` itself.
|
||||||
|
|
||||||
4. **Manual setup:** Users can also set values directly:
|
4. **Manual setup:** Users can also set values directly:
|
||||||
```bash
|
```bash
|
||||||
hermes config set skills.config.wiki.path ~/my-wiki
|
hermes config set skills.config.myplugin.path ~/my-data
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip When to use which
|
:::tip When to use which
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ Skills for academic research, paper discovery, literature review, domain reconna
|
||||||
|-------|-------------|------|
|
|-------|-------------|------|
|
||||||
| `arxiv` | Search and retrieve academic papers from arXiv using their free REST API. No API key needed. Search by keyword, author, category, or ID. Combine with web_extract or the ocr-and-documents skill to read full paper content. | `research/arxiv` |
|
| `arxiv` | Search and retrieve academic papers from arXiv using their free REST API. No API key needed. Search by keyword, author, category, or ID. Combine with web_extract or the ocr-and-documents skill to read full paper content. | `research/arxiv` |
|
||||||
| `blogwatcher` | Monitor blogs and RSS/Atom feeds for updates using the blogwatcher CLI. Add blogs, scan for new articles, and track what you've read. | `research/blogwatcher` |
|
| `blogwatcher` | Monitor blogs and RSS/Atom feeds for updates using the blogwatcher CLI. Add blogs, scan for new articles, and track what you've read. | `research/blogwatcher` |
|
||||||
| `llm-wiki` | Karpathy's LLM Wiki — build and maintain a persistent, interlinked markdown knowledge base. Ingest sources, query compiled knowledge, and lint for consistency. Unlike RAG, the wiki compiles knowledge once and keeps it current. Works as an Obsidian vault. Configurable via `skills.config.wiki.path`. | `research/llm-wiki` |
|
| `llm-wiki` | Karpathy's LLM Wiki — build and maintain a persistent, interlinked markdown knowledge base. Ingest sources, query compiled knowledge, and lint for consistency. Unlike RAG, the wiki compiles knowledge once and keeps it current. Works as an Obsidian vault. Wiki path is controlled by the `WIKI_PATH` env var (defaults to `~/wiki`). | `research/llm-wiki` |
|
||||||
| `domain-intel` | Passive domain reconnaissance using Python stdlib. Subdomain discovery, SSL certificate inspection, WHOIS lookups, DNS records, domain availability checks, and bulk multi-domain analysis. No API keys required. | `research/domain-intel` |
|
| `domain-intel` | Passive domain reconnaissance using Python stdlib. Subdomain discovery, SSL certificate inspection, WHOIS lookups, DNS records, domain availability checks, and bulk multi-domain analysis. No API keys required. | `research/domain-intel` |
|
||||||
| `duckduckgo-search` | Free web search via DuckDuckGo — text, news, images, videos. No API key needed. Prefer the `ddgs` CLI when installed; use the Python DDGS library only after verifying that `ddgs` is available in the current runtime. | `research/duckduckgo-search` |
|
| `duckduckgo-search` | Free web search via DuckDuckGo — text, news, images, videos. No API key needed. Prefer the `ddgs` CLI when installed; use the Python DDGS library only after verifying that `ddgs` is available in the current runtime. | `research/duckduckgo-search` |
|
||||||
| `ml-paper-writing` | Write publication-ready ML/AI papers for NeurIPS, ICML, ICLR, ACL, AAAI, COLM. Use when drafting papers from research repos, structuring arguments, verifying citations, or preparing camera-ready submissions. Includes LaTeX templates, reviewer guidelines, and citation verificatio… | `research/ml-paper-writing` |
|
| `ml-paper-writing` | Write publication-ready ML/AI papers for NeurIPS, ICML, ICLR, ACL, AAAI, COLM. Use when drafting papers from research repos, structuring arguments, verifying citations, or preparing camera-ready submissions. Includes LaTeX templates, reviewer guidelines, and citation verificatio… | `research/ml-paper-writing` |
|
||||||
|
|
|
||||||
|
|
@ -359,8 +359,8 @@ Skills can declare their own configuration settings via their SKILL.md frontmatt
|
||||||
```yaml
|
```yaml
|
||||||
skills:
|
skills:
|
||||||
config:
|
config:
|
||||||
wiki:
|
myplugin:
|
||||||
path: ~/wiki # Used by the llm-wiki skill
|
path: ~/myplugin-data # Example — each skill defines its own keys
|
||||||
```
|
```
|
||||||
|
|
||||||
**How skill settings work:**
|
**How skill settings work:**
|
||||||
|
|
@ -372,7 +372,7 @@ skills:
|
||||||
**Setting values manually:**
|
**Setting values manually:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hermes config set skills.config.wiki.path ~/my-research-wiki
|
hermes config set skills.config.myplugin.path ~/myplugin-data
|
||||||
```
|
```
|
||||||
|
|
||||||
For details on declaring config settings in your own skills, see [Creating Skills — Config Settings](/docs/developer-guide/creating-skills#config-settings-configyaml).
|
For details on declaring config settings in your own skills, see [Creating Skills — Config Settings](/docs/developer-guide/creating-skills#config-settings-configyaml).
|
||||||
|
|
|
||||||
|
|
@ -155,10 +155,10 @@ Skills can also declare non-secret config settings (paths, preferences) stored i
|
||||||
metadata:
|
metadata:
|
||||||
hermes:
|
hermes:
|
||||||
config:
|
config:
|
||||||
- key: wiki.path
|
- key: myplugin.path
|
||||||
description: Path to the wiki directory
|
description: Path to the plugin data directory
|
||||||
default: "~/wiki"
|
default: "~/myplugin-data"
|
||||||
prompt: Wiki directory path
|
prompt: Plugin data directory path
|
||||||
```
|
```
|
||||||
|
|
||||||
Settings are stored under `skills.config` in your config.yaml. `hermes config migrate` prompts for unconfigured settings, and `hermes config show` displays them. When a skill loads, its resolved config values are injected into the context so the agent knows the configured values automatically.
|
Settings are stored under `skills.config` in your config.yaml. `hermes config migrate` prompts for unconfigured settings, and `hermes config show` displays them. When a skill loads, its resolved config values are injected into the context so the agent knows the configured values automatically.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue