* feat(desktop): hover-reveal collapsed chat sidebar as a fixed overlay When the sessions sidebar is collapsed, hovering the left edge now floats it back in as a fixed overlay over the main content instead of just being hidden. The collapsed grid track stays at 0px so the panel never reserves space — it slides over whatever's underneath and retracts on pointer-leave. - PaneShell: new hoverReveal prop. When a pane is collapsed + hoverReveal, render an edge hot-zone + a side-anchored floating panel (absolute, full height, honors any persisted resize width) that slides in on hover/focus. - ChatSidebar: force the (otherwise opacity-0 when collapsed) sidebar fully visible + interactive while the overlay is revealed, via an in-data-[pane-hover-reveal=open] variant. - desktop-controller: opt the chat-sidebar pane into hoverReveal. * feat(desktop): lower window minWidth 900→400 Lets the window shrink to a narrow rail (e.g. for the collapsed hover-reveal sidebar) instead of being floored at 900px. * fix(desktop): render full sidebar content in hover-reveal overlay The hover-reveal overlay showed only the nav rail — session rows, search, pinned/recents were gated behind `sidebarOpen` (false while collapsed), so they never mounted in the floated panel. Add a $sidebarRevealed store the PaneShell overlay drives via a new onHoverRevealChange callback, and gate ChatSidebar's content on `sidebarOpen || sidebarRevealed` (contentVisible) instead of raw open state. The overlay now shows the complete sidebar. * fix(desktop): drop shadow on hover-reveal sidebar overlay * feat(desktop): hover-reveal the file-browser sidebar too The reveal mechanism already lives in the shared Pane primitive — the right rail just opts in with hoverReveal. Its content renders unconditionally, so (unlike the chat sidebar) it needs no extra content-visibility gating. * clean(desktop): tighten hover-reveal pane code KISS pass — flatten the translate ternary, derive a single `revealed`, inline the edge style, drop the redundant set-guard, and trim comments to the house one-liner style. No behavior change. * fix(desktop): stop hiding sidebar nav labels on narrow windows The nav labels (New session, Skills, …) and the ⌘N hint were gated on a viewport breakpoint (max-[46.25rem]:hidden), so shrinking the window hid them even when the sidebar itself was wide — including in the hover-reveal overlay. Drop the gate; the label already truncates (min-w-0 flex-1) so it ellipsizes gracefully in a narrow rail, and contentVisible already hides it when collapsed to the icon rail. * feat(desktop): auto-collapse both sidebars below 600px into hover-reveal Add a Pane `forceCollapsed` prop — collapses the track without writing to the store (so the saved open state restores when the window widens) while keeping hoverReveal alive (unlike `disabled`, which suppresses it). desktop-controller watches (max-width: 600px) and force-collapses the chat sidebar + file browser, so on a narrow window both rails get out of the way and the hover-reveal overlay becomes the way in. * feat(desktop): hover-intent + refined easing for sidebar reveal - Gate the reveal on pointer velocity: the full-height edge hot-zone now only arms on a slow, deliberate pass (<=0.55 px/ms). Fast sweeps toward the titlebar/statusbar — or off the window — blow past the threshold and never trigger, so the wide hit area stops being a nuisance. - Swap the slide easing to cubic-bezier(0.32,0.72,0,1) at 260ms (snappy-out, soft-land) for a more serious-app feel. * fix(desktop): don't reveal sidebar during window resize Resizing the window parks the cursor on the screen edge and fires slow pointermoves over the hot-zone, reading as deliberate intent. Guard the reveal on (a) e.buttons !== 0 — any button-held drag, incl. edge-resize — and (b) a 250ms cooldown after any window resize event. * feat(desktop): hoverIntent-style poll gate + inert contents during slide Replace the single-sample velocity check (too eager — fired on any one slow move, incl. resize drift) with a port of Brian Cherne's hoverIntent: poll the pointer every 90ms and only arm once it has *settled* (moved <5px between two consecutive polls inside the edge zone). Fly-bys, pass-throughs, and resize drift never produce two close samples in a row, so they don't trigger. Also keep the revealed panel's CONTENTS pointer-events-none until the slide-in transition finishes (onTransitionEnd → settled), so you can't misclick a session row mid-animation. Resets on retract. * fix(desktop): no cursor/hit-test leak before reveal settles The edge hot-zone showed cursor:pointer the instant the pointer touched it — before the panel was armed or in view. And contents were inert but the panel itself still hit-tested, so the cursor could flip mid-slide. Fix: hot-zone is cursor-default (it's invisible), and the whole panel is pointer-events-none until revealed && settled, so the cursor never changes or lands on a row before the slide-in finishes. * fix(desktop): geometry-driven close so revealed panel always retracts The revealed panel relied on its own onPointerLeave to close — but a panel that slid in under a still cursor (or whose contents were inert during the slide) never fires enter/leave, so it got stuck open (esp. the file browser). onTransitionEnd also bubbled from the file-tree's own row transitions, tripping the settled flag wrongly. Replace with a document-level pointermove watcher that closes once the cursor leaves the panel's bounding rect + a 24px grace — independent of pointer-events state or what the contents do. Gate interactivity on a simple slide-duration timer (interactive) instead of the fragile transitionEnd, so the cursor still can't flip or land on a row before the panel is in view. * feat(desktop): make sidebar toggle shortcuts reveal when force-collapsed mod+b / mod+j were no-ops on a narrow (force-collapsed) window — they flipped the store but the pane ignores it. Now the toggle handlers also dispatch PANE_TOGGLE_REVEAL_EVENT; a force-collapsed Pane listens (only while overlayActive) and flips its hover-reveal, so the shortcut floats the rail in (and back out) at this responsive breakpoint. * refactor(desktop): name the 600px sidebar collapse breakpoint Hoist the inline '(max-width: 600px)' literal into SIDEBAR_COLLAPSE_BREAKPOINT_PX + SIDEBAR_COLLAPSE_MEDIA_QUERY in layout-constants, so the responsive collapse point is a single named source of truth instead of a magic string in the controller. * tweak(desktop): sidebar auto-collapse breakpoint 600px -> 768px 768 is the standard md breakpoint and a more honest 'no room to dock' point. * tweak(desktop): halve sidebar reveal slide duration 260ms -> 130ms * Revert "tweak(desktop): halve sidebar reveal slide duration 260ms -> 130ms" This reverts commit 6009a132008105f5f871370d554a5097be3090af. * perf(desktop): pre-mount hover-reveal contents to kill slide-in stall The reveal mounted the (heavy, virtualized) sidebar contents in the same frame the slide started, so the browser stalled painting the transform until the mount finished — a ~100-200ms beat before the panel moved, very visible on the instant keyboard toggle (hover masked it via the 90ms intent poll). Report overlayActive (collapsed-overlay mode) rather than the live reveal state to the mount consumer, so contents stay mounted off-screen while collapsed and reveal is a pure transform. Visibility is still driven separately by the data-pane-hover-reveal attr + the slide transform. * fix(desktop): make reveal hotkey spammable Two throttles on the reveal toggle: - The handler fired both the reveal event AND toggleSidebarOpen() per press; the store write hits localStorage synchronously every keystroke + recomputes the grid, janking rapid presses. When collapsed, only dispatch the reveal event (the store toggle was a no-op anyway). - The geometry close-watcher slammed a keyboard-opened panel shut on the first stray pointermove (trackpad jitter), fighting hotkey spam. Keyboard reveals now ignore geometry until the cursor actually enters the panel, then the mouse takes over. * fix(desktop): inset reveal hot-zone past the OS window-resize gutter The hot-zone sat flush at the window edge (left-0/right-0), overlapping the OS resize grab strip — reaching to drag-resize naturally slows the cursor there, which hoverIntent reads as settled and reveals before the resize drag even starts. Inset the hot-zone 8px so the outermost edge stays a pure resize/drag region and only an intentful move just inside it arms a reveal. * fix(desktop): keep reveal hot-zone at edge, gate arming past resize gutter Insetting the hot-zone made it unreachable when moving fast. Instead, anchor the zone flush at the edge (w-4, always captures the pointer) but only ARM the reveal when the cursor settles >=8px in from the edge — so a resize-reach that parks on the outermost OS grab strip never triggers, while a deliberate move into the zone still does. Keeps polling while in the gutter so moving inward still arms. * refactor(desktop): rebuild hover-reveal as pure CSS, delete the JS state machine The hand-rolled pointer state machine (hoverIntent poll, refs, timers, document pointermove geometry-close, interactive gate, resize cooldowns, keyboard-held suppression) was fragile and side/instance-specific — hover broke on the right rail, keyboard toggles triggered phantom animations, resize popped it open. Replace all of it with the native primitive: CSS group-hover drives the slide transform; a transition-delay on enter (instant on leave) is the hover-intent gate (a fast pass-by doesn't dwell long enough to open); a thin edge trigger inset past the OS resize grab strip arms it; and a single `forced` bool (data-forced, toggled by the keyboard event) pins it open. Side-agnostic by construction — group-hover doesn't care which edge or which pane. Net: ~200 lines of imperative pointer logic → ~40 lines of declarative CSS. * fix(desktop): don't animate hover-reveal panel across viewport on side flip Flipping panes changed the off-screen transform from -translateX (off the left) to +translateX (off the right). transition-transform interpolated between them, passing through translate-x-0 (fully on-screen) mid-way — so the hidden panel visibly slid across the window to reach its new hiding spot. Key the panel on side so it remounts off-screen on the new edge with no transition to play. * clean(desktop): tighten hover-reveal markup KISS pass on the CSS-driven reveal: reuse the existing `side` instead of a local `left`, move the static duration/ease to inline style (drop two single-use CSS vars + their arbitrary-value classes, keep only the state-dependent enter-delay var), and trim comments to the house one-liner density. No behavior change. * fix(desktop): inset titlebar past traffic lights when sidebar is force-collapsed The titlebar content inset (clearing the macOS traffic lights) keyed off the stored sidebarOpen/fileBrowserOpen, but below the collapse breakpoint both rails are force-collapsed so the left edge is uncovered while the store still says open — content (the intro wordmark) overflowed under the lights. Gate leftEdgePaneOpen on !narrowViewport using the shared SIDEBAR_COLLAPSE_MEDIA_QUERY. Also rename the now-misleading reveal plumbing to match what it actually does: onHoverRevealChange -> onOverlayActiveChange, $sidebarRevealed -> $sidebarOverlayMounted (+ setter/consumer). It reports/stores collapsed-overlay mode (mount gate), not live reveal state. * feat(desktop): small --nous-shadow lift on revealed hover-reveal panels Add a --nous-shadow token (white-based on light, black-based on dark) and apply it to the floating sidebar panel only while revealed (group-hover / data-forced) so it reads as lifted off the surface. No shadow on the off-screen panel. * feat(desktop): shadow-reveal lift on revealed hover-reveal panels Mirror the --shadow-nous layered falloff into a new --shadow-reveal token whose drop color flips per mode (white on light, black on dark) via --shadow-reveal-raw set in :root / :root.dark. Apply the generated shadow-reveal utility to the floated panel only while revealed (group-hover / data-forced). Leaves the shared --shadow-nous untouched. * feat(desktop): use tuned reveal shadow, drop per-mode token Replace the --shadow-reveal token machinery with Brooklyn's tuned literal (0 -18px 18px -5px #0000003b) inline per-panel via --reveal-shadow, y-offset sign flipped for the right side. Same color both modes. Reverts styles.css to pristine (token removed). * fix(desktop): use the reveal shadow verbatim, don't invert it per side Flipping the y-offset sign for the right side inverted the shadow's direction (cast-up -> cast-down), making it read heavier — not a mirror. The mirror axis for a left/right panel is offset-x, which is 0 here, so both sides take the tuned value as-is: 0 -18px 18px -5px #0000003b. * clean(desktop): hoist reveal shadow to a named const Move the inline reveal-shadow literal to HOVER_REVEAL_SHADOW alongside the other HOVER_REVEAL_* tuning consts; drop the now-stale per-side comment. * fix(desktop): truncate titlebar title before the right tool cluster The session title used a hardcoded max-w-[52vw] that's blind to where the right-side tools start, so it ran under them at narrow widths / with pane tools present. Bound the title container by the same vars the titlebar drag region uses (--titlebar-content-inset + --titlebar-tools-right + --titlebar-tools-width) so it truncates exactly at the cluster's left edge. * fix(desktop): responsive markdown tables — floor width + nowrap headers The wrapper had overflow-x-auto but the table was w-full with auto layout, so instead of scrolling it crushed columns until even header words broke mid-word (Tim/e, Nig/ht). Add a min-w-[18rem] floor so it scrolls horizontally when the column is narrower than readable, and whitespace-nowrap on th so headers never break mid-word. Above the floor it still wraps cells naturally. * fix intro |
||
|---|---|---|
| .github | ||
| .plans | ||
| acp_adapter | ||
| acp_registry | ||
| agent | ||
| apps | ||
| assets | ||
| cron | ||
| datagen-config-examples | ||
| docker | ||
| docs | ||
| gateway | ||
| hermes_cli | ||
| infographic/kanban-db-corruption-defense | ||
| locales | ||
| nix | ||
| optional-mcps | ||
| optional-skills | ||
| packaging/homebrew | ||
| plans | ||
| plugins | ||
| providers | ||
| scripts | ||
| skills | ||
| tests | ||
| tools | ||
| tui_gateway | ||
| ui-tui | ||
| web | ||
| website | ||
| .dockerignore | ||
| .env.example | ||
| .envrc | ||
| .gitattributes | ||
| .gitignore | ||
| .hadolint.yaml | ||
| .mailmap | ||
| AGENTS.md | ||
| batch_runner.py | ||
| cli-config.yaml.example | ||
| cli.py | ||
| constraints-termux.txt | ||
| CONTRIBUTING.md | ||
| docker-compose.windows.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| flake.lock | ||
| flake.nix | ||
| hermes | ||
| hermes-already-has-routines.md | ||
| hermes_bootstrap.py | ||
| hermes_constants.py | ||
| hermes_logging.py | ||
| hermes_state.py | ||
| hermes_time.py | ||
| LICENSE | ||
| MANIFEST.in | ||
| mcp_serve.py | ||
| mini_swe_runner.py | ||
| model_tools.py | ||
| package-lock.json | ||
| package.json | ||
| pyproject.toml | ||
| README.md | ||
| README.ur-pk.md | ||
| README.zh-CN.md | ||
| run_agent.py | ||
| SECURITY.md | ||
| setup-hermes.sh | ||
| setup.py | ||
| toolset_distributions.py | ||
| toolsets.py | ||
| trajectory_compressor.py | ||
| utils.py | ||
| uv.lock | ||
Hermes Agent ☤
The self-improving AI agent built by Nous Research. It's the only agent with a built-in learning loop — it creates skills from experience, improves them during use, nudges itself to persist knowledge, searches its own past conversations, and builds a deepening model of who you are across sessions. Run it on a $5 VPS, a GPU cluster, or serverless infrastructure that costs nearly nothing when idle. It's not tied to your laptop — talk to it from Telegram while it works on a cloud VM.
Use any model you want — Nous Portal, OpenRouter (200+ models), NovitaAI (AI-native cloud for Model API, Agent Sandbox, and GPU Cloud), NVIDIA NIM (Nemotron), Xiaomi MiMo, z.ai/GLM, Kimi/Moonshot, MiniMax, Hugging Face, OpenAI, or your own endpoint. Switch with hermes model — no code changes, no lock-in.
| A real terminal interface | Full TUI with multiline editing, slash-command autocomplete, conversation history, interrupt-and-redirect, and streaming tool output. |
| Lives where you do | Telegram, Discord, Slack, WhatsApp, Signal, and CLI — all from a single gateway process. Voice memo transcription, cross-platform conversation continuity. |
| A closed learning loop | Agent-curated memory with periodic nudges. Autonomous skill creation after complex tasks. Skills self-improve during use. FTS5 session search with LLM summarization for cross-session recall. Honcho dialectic user modeling. Compatible with the agentskills.io open standard. |
| Scheduled automations | Built-in cron scheduler with delivery to any platform. Daily reports, nightly backups, weekly audits — all in natural language, running unattended. |
| Delegates and parallelizes | Spawn isolated subagents for parallel workstreams. Write Python scripts that call tools via RPC, collapsing multi-step pipelines into zero-context-cost turns. |
| Runs anywhere, not just your laptop | Six terminal backends — local, Docker, SSH, Singularity, Modal, and Daytona. Daytona and Modal offer serverless persistence — your agent's environment hibernates when idle and wakes on demand, costing nearly nothing between sessions. Run it on a $5 VPS or a GPU cluster. |
| Research-ready | Batch trajectory generation, trajectory compression for training the next generation of tool-calling models. |
Quick Install
Linux, macOS, WSL2, Termux
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
Windows (native, PowerShell)
Heads up: Native Windows runs Hermes without WSL — CLI, gateway, TUI, and tools all work natively. If you'd rather use WSL2, the Linux/macOS one-liner above works there too. Found a bug? Please file issues.
Run this in PowerShell:
iex (irm https://hermes-agent.nousresearch.com/install.ps1)
The installer handles everything: uv, Python 3.11, Node.js, ripgrep, ffmpeg, and a portable Git Bash (MinGit, unpacked to %LOCALAPPDATA%\hermes\git — no admin required, completely isolated from any system Git install). Hermes uses this bundled Git Bash to run shell commands.
If you already have Git installed, the installer detects it and uses that instead. Otherwise a ~45MB MinGit download is all you need — it won't touch or interfere with any system Git.
Android / Termux: The tested manual path is documented in the Termux guide. On Termux, Hermes installs a curated
.[termux]extra because the full.[all]extra currently pulls Android-incompatible voice dependencies.Windows: Native Windows is fully supported — the PowerShell one-liner above installs everything. If you'd rather use WSL2, the Linux command works there too. Native Windows install lives under
%LOCALAPPDATA%\hermes; WSL2 installs under~/.hermesas on Linux. The only Hermes feature that currently needs WSL2 specifically is the browser-based dashboard chat pane (it uses a POSIX PTY — classic CLI and gateway both run natively).
After installation:
source ~/.bashrc # reload shell (or: source ~/.zshrc)
hermes # start chatting!
Getting Started
hermes # Interactive CLI — start a conversation
hermes model # Choose your LLM provider and model
hermes tools # Configure which tools are enabled
hermes config set # Set individual config values
hermes gateway # Start the messaging gateway (Telegram, Discord, etc.)
hermes setup # Run the full setup wizard (configures everything at once)
hermes claw migrate # Migrate from OpenClaw (if coming from OpenClaw)
hermes update # Update to the latest version
hermes doctor # Diagnose any issues
Skip the API-key collection — Nous Portal
Hermes works with whatever provider you want — that's not changing. But if you'd rather not collect five separate API keys for the model, web search, image generation, TTS, and a cloud browser, Nous Portal covers all of them under one subscription:
- 300+ models — pick any of them with
/model <name> - Tool Gateway — web search (Firecrawl), image generation (FAL), text-to-speech (OpenAI), cloud browser (Browser Use), all routed through your sub. No extra accounts.
One command from a fresh install:
hermes setup --portal
That logs you in via OAuth, sets Nous as your provider, and turns on the Tool Gateway. Check what's wired up any time with hermes portal info. Full details on the Tool Gateway docs page.
You can still bring your own keys per-tool whenever you want — the gateway is per-backend, not all-or-nothing.
CLI vs Messaging Quick Reference
Hermes has two entry points: start the terminal UI with hermes, or run the gateway and talk to it from Telegram, Discord, Slack, WhatsApp, Signal, or Email. Once you're in a conversation, many slash commands are shared across both interfaces.
| Action | CLI | Messaging platforms |
|---|---|---|
| Start chatting | hermes |
Run hermes gateway setup + hermes gateway start, then send the bot a message |
| Start fresh conversation | /new or /reset |
/new or /reset |
| Change model | /model [provider:model] |
/model [provider:model] |
| Set a personality | /personality [name] |
/personality [name] |
| Retry or undo the last turn | /retry, /undo |
/retry, /undo |
| Compress context / check usage | /compress, /usage, /insights [--days N] |
/compress, /usage, /insights [days] |
| Browse skills | /skills or /<skill-name> |
/<skill-name> |
| Interrupt current work | Ctrl+C or send a new message |
/stop or send a new message |
| Platform-specific status | /platforms |
/status, /sethome |
For the full command lists, see the CLI guide and the Messaging Gateway guide.
Documentation
All documentation lives at hermes-agent.nousresearch.com/docs:
| Section | What's Covered |
|---|---|
| Quickstart | Install → setup → first conversation in 2 minutes |
| CLI Usage | Commands, keybindings, personalities, sessions |
| Configuration | Config file, providers, models, all options |
| Messaging Gateway | Telegram, Discord, Slack, WhatsApp, Signal, Home Assistant |
| Security | Command approval, DM pairing, container isolation |
| Tools & Toolsets | 40+ tools, toolset system, terminal backends |
| Skills System | Procedural memory, Skills Hub, creating skills |
| Memory | Persistent memory, user profiles, best practices |
| MCP Integration | Connect any MCP server for extended capabilities |
| Cron Scheduling | Scheduled tasks with platform delivery |
| Context Files | Project context that shapes every conversation |
| Architecture | Project structure, agent loop, key classes |
| Contributing | Development setup, PR process, code style |
| CLI Reference | All commands and flags |
| Environment Variables | Complete env var reference |
Migrating from OpenClaw
If you're coming from OpenClaw, Hermes can automatically import your settings, memories, skills, and API keys.
During first-time setup: The setup wizard (hermes setup) automatically detects ~/.openclaw and offers to migrate before configuration begins.
Anytime after install:
hermes claw migrate # Interactive migration (full preset)
hermes claw migrate --dry-run # Preview what would be migrated
hermes claw migrate --preset user-data # Migrate without secrets
hermes claw migrate --overwrite # Overwrite existing conflicts
What gets imported:
- SOUL.md — persona file
- Memories — MEMORY.md and USER.md entries
- Skills — user-created skills →
~/.hermes/skills/openclaw-imports/ - Command allowlist — approval patterns
- Messaging settings — platform configs, allowed users, working directory
- API keys — allowlisted secrets (Telegram, OpenRouter, OpenAI, Anthropic, ElevenLabs)
- TTS assets — workspace audio files
- Workspace instructions — AGENTS.md (with
--workspace-target)
See hermes claw migrate --help for all options, or use the openclaw-migration skill for an interactive agent-guided migration with dry-run previews.
Contributing
We welcome contributions! See the Contributing Guide for development setup, code style, and PR process.
Quick start for contributors — clone and go with setup-hermes.sh:
git clone https://github.com/NousResearch/hermes-agent.git
cd hermes-agent
./setup-hermes.sh # installs uv, creates venv, installs .[all], symlinks ~/.local/bin/hermes
./hermes # auto-detects the venv, no need to `source` first
Manual path (equivalent to the above):
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv .venv --python 3.11
source .venv/bin/activate
uv pip install -e ".[all,dev]"
scripts/run_tests.sh
Community
- 💬 Discord
- 📚 Skills Hub
- 🐛 Issues
- 🔌 computer-use-linux — Linux desktop-control MCP server for Hermes and other MCP hosts, with AT-SPI accessibility trees, Wayland/X11 input, screenshots, and compositor window targeting.
- 🔌 HermesClaw — Community WeChat bridge: Run Hermes Agent and OpenClaw on the same WeChat account.
License
MIT — see LICENSE.
Built by Nous Research.