Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# Get your key at: https://openrouter.ai/keys
# OPENROUTER_API_KEY=

# Default model is configured in ~/.hermes/config.yaml (model.default).
# Default model is configured in ~/.kora/config.yaml (model.default).
# Use 'hermes model' or 'hermes setup' to change it.
# LLM_MODEL is no longer read from .env — this line is kept for reference only.
# LLM_MODEL=anthropic/claude-opus-4.6
Expand Down Expand Up @@ -167,7 +167,7 @@
# TERMINAL TOOL CONFIGURATION
# =============================================================================
# Backend type: "local", "singularity", "docker", "modal", or "ssh"
# Terminal backend is configured in ~/.hermes/config.yaml (terminal.backend).
# Terminal backend is configured in ~/.kora/config.yaml (terminal.backend).
# Use 'hermes setup' or 'hermes config set terminal.backend docker' to change.
# Supported: local, docker, singularity, modal, ssh
#
Expand Down Expand Up @@ -390,7 +390,7 @@ IMAGE_TOOLS_DEBUG=false
# When conversation approaches model's context limit, middle turns are
# automatically summarized to free up space.
#
# Context compression is configured in ~/.hermes/config.yaml under compression:
# Context compression is configured in ~/.kora/config.yaml under compression:
# CONTEXT_COMPRESSION_ENABLED=true # Enable auto-compression (default: true)
# CONTEXT_COMPRESSION_THRESHOLD=0.85 # Compress at 85% of context limit
# Model is set via compression.summary_model in config.yaml (default: google/gemini-3-flash-preview)
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/setup_help.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,4 @@ body:
placeholder: |
- Ran `hermes update`
- Tried reinstalling with `pip install -e ".[all]"`
- Checked that OPENROUTER_API_KEY is set in ~/.hermes/.env
- Checked that OPENROUTER_API_KEY is set in ~/.kora/.env
2 changes: 1 addition & 1 deletion .github/actions/hermes-smoke-test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ runs:
# The image runs as the hermes user (UID 10000). GitHub Actions
# creates /tmp/hermes-test root-owned by default, which hermes
# can't write to — chown it to match the in-container UID before
# bind-mounting. Real users doing `docker run -v ~/.hermes:...`
# bind-mounting. Real users doing `docker run -v ~/.kora:...`
# with their own UID hit the same issue and have their own
# remediations (HERMES_UID env var, or chown locally).
mkdir -p /tmp/hermes-test
Expand Down
2 changes: 1 addition & 1 deletion .plans/openai-api-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ usable as a backend for all of them — no custom adapters needed.
```

A user would:
1. Set `API_SERVER_ENABLED=true` in `~/.hermes/.env`
1. Set `API_SERVER_ENABLED=true` in `~/.kora/.env`
2. Run `hermes gateway` (API server starts alongside Telegram/Discord/etc.)
3. Point Open WebUI (or any frontend) at `http://localhost:8642/v1`
4. Chat with hermes-agent through any OpenAI-compatible UI
Expand Down
58 changes: 29 additions & 29 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ source .venv/bin/activate # or: source venv/bin/activate
```

`scripts/run_tests.sh` probes `.venv` first, then `venv`, then
`$HOME/.hermes/hermes-agent/venv` (for worktrees that share a venv with the
`$HOME/.kora/hermes-agent/venv` (for worktrees that share a venv with the
main checkout).

## Project Structure
Expand Down Expand Up @@ -61,8 +61,8 @@ hermes-agent/
└── tests/ # Pytest suite (~17k tests across ~900 files as of May 2026)
```

**User config:** `~/.hermes/config.yaml` (settings), `~/.hermes/.env` (API keys only).
**Logs:** `~/.hermes/logs/` — `agent.log` (INFO+), `errors.log` (WARNING+),
**User config:** `~/.kora/config.yaml` (settings), `~/.kora/.env` (API keys only).
**Logs:** `~/.kora/logs/` — `agent.log` (INFO+), `errors.log` (WARNING+),
`gateway.log` when running the gateway. Profile-aware via `get_hermes_home()`.
Browse with `hermes logs [--follow] [--level ...] [--session ...]`.

Expand Down Expand Up @@ -147,7 +147,7 @@ Reasoning content is stored in `assistant_msg["reasoning"]`.
- `load_cli_config()` in cli.py merges hardcoded defaults + user config YAML
- **Skin engine** (`hermes_cli/skin_engine.py`) — data-driven CLI theming; initialized from `display.skin` config key at startup; skins customize banner colors, spinner faces/verbs/wings, tool prefix, response box, branding text
- `process_command()` is a method on `HermesCLI` — dispatches on canonical command name resolved via `resolve_command()` from the central registry
- Skill slash commands: `agent/skill_commands.py` scans `~/.hermes/skills/`, injects as **user message** (not system prompt) to preserve prompt caching
- Skill slash commands: `agent/skill_commands.py` scans `~/.kora/skills/`, injects as **user message** (not system prompt) to preserve prompt caching

### Slash Command Registry (`hermes_cli/commands.py`)

Expand Down Expand Up @@ -263,8 +263,8 @@ The dashboard embeds the real `hermes --tui` — **not** a rewrite. See `hermes
## Adding New Tools

For most custom or local-only tools, do **not** edit Hermes core. Use the plugin
route instead: create `~/.hermes/plugins/<name>/plugin.yaml` and
`~/.hermes/plugins/<name>/__init__.py`, then register tools with
route instead: create `~/.kora/plugins/<name>/plugin.yaml` and
`~/.kora/plugins/<name>/__init__.py`, then register tools with
`ctx.register_tool(...)`. Plugin toolsets are discovered automatically and can be
enabled or disabled without touching `tools/` or `toolsets.py`.

Expand Down Expand Up @@ -302,7 +302,7 @@ The registry handles schema collection, dispatch, availability checking, and err

**Path references in tool schemas**: If the schema description mentions file paths (e.g. default output directories), use `display_hermes_home()` to make them profile-aware. The schema is generated at import time, which is after `_apply_profile_override()` sets `HERMES_HOME`.

**State files**: If a tool stores persistent state (caches, logs, checkpoints), use `get_hermes_home()` for the base directory — never `Path.home() / ".hermes"`. This ensures each profile gets its own state.
**State files**: If a tool stores persistent state (caches, logs, checkpoints), use `get_hermes_home()` for the base directory — never `Path.home() / ".kora"`. This ensures each profile gets its own state.

**Agent-level tools** (todo, memory): intercepted by `run_agent.py` before `handle_function_call()`. See `tools/todo_tool.py` for the pattern.

Expand Down Expand Up @@ -403,7 +403,7 @@ The skin engine (`hermes_cli/skin_engine.py`) provides data-driven CLI visual cu

```
hermes_cli/skin_engine.py # SkinConfig dataclass, built-in skins, YAML loader
~/.hermes/skins/*.yaml # User-installed custom skins (drop-in)
~/.kora/skins/*.yaml # User-installed custom skins (drop-in)
```

- `init_skin_from_config()` — called at CLI startup, reads `display.skin` from config
Expand Down Expand Up @@ -457,7 +457,7 @@ Add to `_BUILTIN_SKINS` dict in `hermes_cli/skin_engine.py`:

### User skins (YAML)

Users create `~/.hermes/skins/<name>.yaml`:
Users create `~/.kora/skins/<name>.yaml`:

```yaml
name: cyberpunk
Expand Down Expand Up @@ -488,11 +488,11 @@ Activate with `/skin cyberpunk` or `display.skin: cyberpunk` in config.yaml.

Hermes has two plugin surfaces. Both live under `plugins/` in the repo so
repo-shipped plugins can be discovered alongside user-installed ones in
`~/.hermes/plugins/` and pip-installed entry points.
`~/.kora/plugins/` and pip-installed entry points.

### General plugins (`hermes_cli/plugins.py` + `plugins/<name>/`)

`PluginManager` discovers plugins from `~/.hermes/plugins/`, `./.hermes/plugins/`,
`PluginManager` discovers plugins from `~/.kora/plugins/`, `./.kora/plugins/`,
and pip entry points. Each plugin exposes a `register(ctx)` function that
can:

Expand Down Expand Up @@ -538,7 +538,7 @@ honcho argparse from `main.py` for exactly this reason.
**No new in-tree memory providers (policy, May 2026):** the set of
built-in memory providers under `plugins/memory/` is closed. New memory
backends must ship as **standalone plugin repos** that users install
into `~/.hermes/plugins/` (or via pip entry points) — they implement
into `~/.kora/plugins/` (or via pip entry points) — they implement
the same `MemoryProvider` ABC, register through the same discovery
path, and integrate via `hermes memory setup` / `post_setup()` without
landing in this tree. PRs that add a new directory under
Expand Down Expand Up @@ -751,15 +751,15 @@ work that must outlive the current turn, use `cronjob` or

Background skill-maintenance system that tracks usage on agent-created
skills and auto-archives stale ones. Users never lose skills; archives
go to `~/.hermes/skills/.archive/` and are restorable.
go to `~/.kora/skills/.archive/` and are restorable.

- **Core:** `agent/curator.py` (review loop, auto-transitions, LLM review
prompt) + `agent/curator_backup.py` (pre-run tar.gz snapshots).
- **CLI:** `hermes_cli/curator.py` wires `hermes curator <verb>` where
verbs are: `status`, `run`, `pause`, `resume`, `pin`, `unpin`,
`archive`, `restore`, `prune`, `backup`, `rollback`.
- **Telemetry:** `tools/skill_usage.py` owns the sidecar
`~/.hermes/skills/.usage.json` — per-skill `use_count`, `view_count`,
`~/.kora/skills/.usage.json` — per-skill `use_count`, `view_count`,
`patch_count`, `last_activity_at`, `state` (active / stale /
archived), `pinned`.

Expand Down Expand Up @@ -806,7 +806,7 @@ Hardening invariants:
cannot monopolize the scheduler.
- Catchup window: half the job's period, clamped to 120s–2h.
- Grace window: 120s for one-shot jobs whose fire time was missed.
- File lock at `~/.hermes/cron/.tick.lock` prevents duplicate ticks
- File lock at `~/.kora/cron/.tick.lock` prevents duplicate ticks
across processes.
- Cron sessions pass `skip_memory=True` by default; memory providers
intentionally do not run during cron.
Expand Down Expand Up @@ -900,36 +900,36 @@ automatically scope to the active profile.
### Rules for profile-safe code

1. **Use `get_hermes_home()` for all HERMES_HOME paths.** Import from `hermes_constants`.
NEVER hardcode `~/.hermes` or `Path.home() / ".hermes"` in code that reads/writes state.
NEVER hardcode `~/.kora` or `Path.home() / ".kora"` in code that reads/writes state.
```python
# GOOD
from hermes_constants import get_hermes_home
config_path = get_hermes_home() / "config.yaml"

# BAD — breaks profiles
config_path = Path.home() / ".hermes" / "config.yaml"
config_path = Path.home() / ".kora" / "config.yaml"
```

2. **Use `display_hermes_home()` for user-facing messages.** Import from `hermes_constants`.
This returns `~/.hermes` for default or `~/.hermes/profiles/<name>` for profiles.
This returns `~/.kora` for default or `~/.kora/profiles/<name>` for profiles.
```python
# GOOD
from hermes_constants import display_hermes_home
print(f"Config saved to {display_hermes_home()}/config.yaml")

# BAD — shows wrong path for profiles
print("Config saved to ~/.hermes/config.yaml")
print("Config saved to ~/.kora/config.yaml")
```

3. **Module-level constants are fine** — they cache `get_hermes_home()` at import time,
which is AFTER `_apply_profile_override()` sets the env var. Just use `get_hermes_home()`,
not `Path.home() / ".hermes"`.
not `Path.home() / ".kora"`.

4. **Tests that mock `Path.home()` must also set `HERMES_HOME`** — since code now uses
`get_hermes_home()` (reads env var), not `Path.home() / ".hermes"`:
`get_hermes_home()` (reads env var), not `Path.home() / ".kora"`:
```python
with patch.object(Path, "home", return_value=tmp_path), \
patch.dict(os.environ, {"HERMES_HOME": str(tmp_path / ".hermes")}):
patch.dict(os.environ, {"HERMES_HOME": str(tmp_path / ".kora")}):
...
```

Expand All @@ -940,15 +940,15 @@ automatically scope to the active profile.
See `gateway/platforms/telegram.py` for the canonical pattern.

6. **Profile operations are HOME-anchored, not HERMES_HOME-anchored** — `_get_profiles_root()`
returns `Path.home() / ".hermes" / "profiles"`, NOT `get_hermes_home() / "profiles"`.
returns `Path.home() / ".kora" / "profiles"`, NOT `get_hermes_home() / "profiles"`.
This is intentional — it lets `hermes -p coder profile list` see all profiles regardless
of which one is active.

## Known Pitfalls

### DO NOT hardcode `~/.hermes` paths
### DO NOT hardcode `~/.kora` paths
Use `get_hermes_home()` from `hermes_constants` for code paths. Use `display_hermes_home()`
for user-facing print/log messages. Hardcoding `~/.hermes` breaks profiles — each profile
for user-facing print/log messages. Hardcoding `~/.kora` breaks profiles — each profile
has its own `HERMES_HOME` directory. This was the source of 5 bugs fixed in PR #3575.

### DO NOT introduce new `simple_term_menu` usage
Expand Down Expand Up @@ -991,16 +991,16 @@ Unused code that was never shipped was dead for a reason. Before wiring an
unused module into a live code path, E2E test the real resolution chain
with actual imports (not mocks) against a temp `HERMES_HOME`.

### Tests must not write to `~/.hermes/`
The `_isolate_hermes_home` autouse fixture in `tests/conftest.py` redirects `HERMES_HOME` to a temp dir. Never hardcode `~/.hermes/` paths in tests.
### Tests must not write to `~/.kora/`
The `_isolate_hermes_home` autouse fixture in `tests/conftest.py` redirects `HERMES_HOME` to a temp dir. Never hardcode `~/.kora/` paths in tests.

**Profile tests**: When testing profile features, also mock `Path.home()` so that
`_get_profiles_root()` and `_get_default_hermes_home()` resolve within the temp dir.
Use the pattern from `tests/hermes_cli/test_profiles.py`:
```python
@pytest.fixture
def profile_env(tmp_path, monkeypatch):
home = tmp_path / ".hermes"
home = tmp_path / ".kora"
home.mkdir()
monkeypatch.setattr(Path, "home", lambda: tmp_path)
monkeypatch.setenv("HERMES_HOME", str(home))
Expand Down Expand Up @@ -1031,7 +1031,7 @@ Five real sources of local-vs-CI drift the script closes:
| | Without wrapper | With wrapper |
|---|---|---|
| Provider API keys | Whatever is in your env (auto-detects pool) | All `*_API_KEY`/`*_TOKEN`/etc. unset |
| HOME / `~/.hermes/` | Your real config+auth.json | Temp dir per test |
| HOME / `~/.kora/` | Your real config+auth.json | Temp dir per test |
| Timezone | Local TZ (PDT etc.) | UTC |
| Locale | Whatever is set | C.UTF-8 |
| xdist workers | `-n auto` = all cores (20+ on a workstation) | `-n 4` matching CI |
Expand Down
40 changes: 20 additions & 20 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ If your skill is specialized, community-contributed, or niche, it's better suite

## Memory Providers: Ship as a Standalone Plugin

**We are no longer accepting new memory providers into this repo.** The set of built-in providers under `plugins/memory/` (honcho, mem0, supermemory, byterover, hindsight, holographic, openviking, retaindb) is closed. If you want to add a new memory backend, publish it as a **standalone plugin repo** that users install into `~/.hermes/plugins/` (or via a pip entry point).
**We are no longer accepting new memory providers into this repo.** The set of built-in providers under `plugins/memory/` (honcho, mem0, supermemory, byterover, hindsight, holographic, openviking, retaindb) is closed. If you want to add a new memory backend, publish it as a **standalone plugin repo** that users install into `~/.kora/plugins/` (or via a pip entry point).

Standalone memory plugins:

Expand Down Expand Up @@ -98,12 +98,12 @@ npm install
### Configure for development

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

# Add at minimum an LLM provider key:
echo "OPENROUTER_API_KEY=***" >> ~/.hermes/.env
echo "OPENROUTER_API_KEY=***" >> ~/.kora/.env
```

### Run
Expand Down Expand Up @@ -191,28 +191,28 @@ hermes-agent/
│ ├── install.ps1 # Windows PowerShell installer
│ └── whatsapp-bridge/ # Node.js WhatsApp bridge (Baileys)
├── skills/ # Bundled skills (copied to ~/.hermes/skills/ on install)
├── skills/ # Bundled skills (copied to ~/.kora/skills/ on install)
├── optional-skills/ # Official optional skills (discoverable via hub, not activated by default)
├── tests/ # Test suite
├── website/ # Documentation site (hermes-agent.nousresearch.com)
├── cli-config.yaml.example # Example configuration (copied to ~/.hermes/config.yaml)
├── cli-config.yaml.example # Example configuration (copied to ~/.kora/config.yaml)
└── AGENTS.md # Development guide for AI coding assistants
```

### User configuration (stored in `~/.hermes/`)
### User configuration (stored in `~/.kora/`)

| Path | Purpose |
|------|---------|
| `~/.hermes/config.yaml` | Settings (model, terminal, toolsets, compression, etc.) |
| `~/.hermes/.env` | API keys and secrets |
| `~/.hermes/auth.json` | OAuth credentials (Nous Portal) |
| `~/.hermes/skills/` | All active skills (bundled + hub-installed + agent-created) |
| `~/.hermes/memories/` | Persistent memory (MEMORY.md, USER.md) |
| `~/.hermes/state.db` | SQLite session database |
| `~/.hermes/sessions/` | JSON session logs |
| `~/.hermes/cron/` | Scheduled job data |
| `~/.hermes/whatsapp/session/` | WhatsApp bridge credentials |
| `~/.kora/config.yaml` | Settings (model, terminal, toolsets, compression, etc.) |
| `~/.kora/.env` | API keys and secrets |
| `~/.kora/auth.json` | OAuth credentials (Nous Portal) |
| `~/.kora/skills/` | All active skills (bundled + hub-installed + agent-created) |
| `~/.kora/memories/` | Persistent memory (MEMORY.md, USER.md) |
| `~/.kora/state.db` | SQLite session database |
| `~/.kora/sessions/` | JSON session logs |
| `~/.kora/cron/` | Scheduled job data |
| `~/.kora/whatsapp/session/` | WhatsApp bridge credentials |

---

Expand All @@ -239,7 +239,7 @@ User message → AIAgent._run_agent_loop()

- **Self-registering tools**: Each tool file calls `registry.register()` at import time. `model_tools.py` triggers discovery by importing all tool modules.
- **Toolset grouping**: Tools are grouped into toolsets (`web`, `terminal`, `file`, `browser`, etc.) that can be enabled/disabled per platform.
- **Session persistence**: All conversations are stored in SQLite (`hermes_state.py`) with full-text search and unique session titles. JSON logs go to `~/.hermes/sessions/`.
- **Session persistence**: All conversations are stored in SQLite (`hermes_state.py`) with full-text search and unique session titles. JSON logs go to `~/.kora/sessions/`.
- **Ephemeral injection**: System prompts and prefill messages are injected at API call time, never persisted to the database or logs.
- **Provider abstraction**: The agent works with any OpenAI-compatible API. Provider resolution happens at init time (Nous Portal OAuth, OpenRouter API key, or custom endpoint).
- **Provider routing**: When using OpenRouter, `provider_routing` in config.yaml controls provider selection (sort by throughput/latency/price, allow/ignore specific providers, data retention policies). These are injected as `extra_body.provider` in API requests.
Expand Down Expand Up @@ -463,7 +463,7 @@ prerequisites:
commands: [curl, jq] # Advisory CLI checks
```

Gateway and messaging sessions never collect secrets in-band; they instruct the user to run `hermes setup` or update `~/.hermes/.env` locally.
Gateway and messaging sessions never collect secrets in-band; they instruct the user to run `hermes setup` or update `~/.kora/.env` locally.

**When to declare required environment variables:**
- The skill uses an API key or token that should be collected securely at load time
Expand Down Expand Up @@ -542,7 +542,7 @@ Hermes uses a data-driven skin system — no code changes needed to add a new sk

**Option A: User skin (YAML file)**

Create `~/.hermes/skins/<name>.yaml`:
Create `~/.kora/skins/<name>.yaml`:

```yaml
name: mytheme
Expand Down
Loading