This root file is a hub: what applies everywhere, then a routing table. Each area's AGENTS.md
loads automatically when you work in that directory; read it before editing there.
python scripts/check caps this file at 12k chars and every root-to-area chain at 30k: long form
goes in website/docs/developer-guide/.
Never give up on the right solution.
Hermes is a personal AI agent that runs one agent core across a CLI, a TUI, an Electron desktop app, a web dashboard, ACP editors and a messaging gateway (25+ platform plugins). It is extended through plugins and skills, not by growing the core.
Two invariants shape almost every design decision and are the lens for reviewing any change:
- Per-conversation prompt caching is sacred. Mutating past context, swapping toolsets,
reloading memories or rebuilding the system prompt mid-conversation breaks the cached prefix and
multiplies the user's cost; the ONE exception is context compression. Slash commands that change
system-prompt state defer to the next session, with an opt-in
--now(/skills install --now). - The core is a narrow waist; capability lives at the edges. Every model tool is sent on every API call, so the bar for a new core tool is high. New capability should arrive as a CLI command + skill, a service-gated tool, or a plugin — not as core surface.
A change to the stock system prompt or tools[] fails tests/ci/test_prompt_surface.py until
scripts/ci/prompt_surface.py render is committed with it, and that snapshot
(tests/fixtures/prompt_surface/) needs a hermes-agent-core approval to merge.
The project's intent layer, for contributors and for the triage sweeper (which may only close on
implemented_on_main, cannot_reproduce or incoherent; taste-based closes are a maintainer's
call, and when in doubt a PR stays open). Long form with examples:
website/docs/developer-guide/contributing.md § Contribution rubric.
Wanted: real bug fixes (repro on main, the exact line, the whole class incl. sibling paths);
reach at the edges (adapters, providers, models, UI features) wired into the existing setup UX;
god-file → module refactors; extending before duplicating (3+ PRs in one category → an ABC +
orchestrator); behaviour-contract tests; E2E with real imports against a temp HERMES_HOME for
resolution, config, security and I/O changes; salvage by cherry-pick so authorship survives.
Rejected even when well-built: hooks with no concrete consumer; new HERMES_* env vars for
non-secret config (.env is secrets only, behaviour goes in config.yaml); a new core tool when
terminal + file or a skill already does the job; offset/limit pagination on instructional tools;
"fixes" that destroy the feature they secure; outbound telemetry without an opt-in gate;
change-detector tests; plugins that touch core files; third-party products in the core tree (ship a
standalone plugin repo).
Before you call it a bug, verify the claim AND the intent (git log -p -S "<symbol>"): the
isolation is often the design (profiles are islands on purpose), an absence can be load-bearing,
and a fix that cannot point to the line where the bug manifests has an unverified premise.
Security: SECURITY.md is the scope authority. A §3.1 finding goes private (GitHub Security
Advisories or security@nousresearch.com), never into a public issue, PR, commit or comment; §3.2
hardening is ordinary public work. Name the §2 boundary crossed, with a repro on main.
Footprint ladder (take the highest rung that solves it): extend existing code → CLI command +
skill → service-gated tool (check_fn answers reachability/opt-in, never per-session surface) →
plugin → MCP server in the catalog → new core tool (fundamental, broadly useful, unreachable
otherwise).
source ./activate (activate.fish, activate.ps1) provisions and activates the PM environment; pick an isolated HERMES_HOME/HERMES_RUNTIME_DIR first
(website/docs/reference/package-management.md#developer-workflow). Tests need the separate test
environment in CONTRIBUTING.md. python scripts/check (just check) runs every blocking
lint check CI runs, with CI's pinned tools; --install-hook pre-push runs it on every push (re-run
after pulling to refresh the hook). just lists every developer command (lefthook calls the same
recipes); a new command is a scripts/ file plus a recipe. The HX0xx/PS-P0x rules and their
fixes are in scripts/code_health/config.py; # noqa does not waive a finding,
# health: allow <RULE> -- <why> does.
The filesystem is canonical. Load-bearing entry points:
hermes-agent/
├── run_agent.py # AIAgent facade; the turn loop lives in agent/turn_*.py
├── model_tools.py # Tool orchestration, handle_function_call(); discovery in tools/registry.py
├── toolsets.py # TOOLSETS dict, _HERMES_CORE_TOOLS
├── cli.py # HermesCLI (REPL, slash dispatch) + hermes_cli/cli_*_mixin.py
├── hermes_state.py # SessionDB facade; hermes_state_*.py siblings
├── hermes_constants.py # get_hermes_home(), display_hermes_home() — profile-aware paths
├── scripts/ # check, run_tests.sh, code_health/, ci/
├── evals/ # live-model A/B and probe harnesses
└── website/ # Docusaurus docs (developer-guide/ holds the long-form area docs)
User state: ~/.hermes/config.yaml (settings), .env (secrets only), logs/ (hermes logs);
all profile-aware via get_hermes_home().
Every former god file is a facade (public entry points + the names other packages import) plus
siblings <stem>_<topic>.py, each owning one topic (the tree above, gateway/run.py,
tools/mcp_tool.py, hermes_cli/kanban.py, hermes_cli/web_server.py).
- Find code by topic:
grep -rn "def name" <dir>/<stem>_*.py, not by reading the facade. - Siblings late-import the facade inside functions; never a module-level cycle.
- Patch where production reads: a sibling doing
from <facade> import nameinside the function makes the facade the seam; a patch on the defining module passes silently. - Size and complexity are ratcheted per unit: new functions CC ≤ 20, ≤ 300 lines, nesting ≤ 6; files ≤ 2,000 lines; units already over may only go down (move a function into a sibling to offset growth, or put new tests in a new test file). Behaviour goes in a sibling, never a facade; name ladders become a dict → handler.
- No re-export shims for internal moves; internal paths are not API. Moving a symbol means
fixing its docs in the same PR (grep
website/docs,skills/, everyAGENTS.md).
- No defensive clutter: no wrappers or
try/except: passaround code that cannot fail, no flags nobody sets, no dead code wired in without E2E proof. Comments keep the WHY, cut the WHAT. - Never infer process identity from argv substrings; use
gateway.status.looks_like_gateway_command_line/hermes_cli.update_cmd._hermes_holder_subcommand(HX003). - Never hardcode
~/.hermes:get_hermes_home()for paths,display_hermes_home()for text (HX001)._get_profiles_root()is HOME-anchored on purpose. - One process serves many profiles. Code that runs outside a turn (boot probes, eviction,
tickers, deferred callbacks, RPC methods, thread hops, child spawns) binds the owning profile scope
explicitly;
os.environ, module globals and import-time values hold the launch profile's, so an unbound read is a silent default-profile leak. Binding points and seams:gateway/AGENTS.md§ Profile scope (HX002/HX004/HX005/HX012, PS-P05/P06). - Machine facts and executable lookup go through
hermes_platform. - Dependencies carry upper bounds (
>=floor,<next_major; git URLs and Actions pinned to a SHA); after editingpyproject.tomlrunhermes pm lockand commituv.lock; never mutate a Hermes environment with raw pip/uv. - TypeScript (desktop, TUI): feature-owned nanostores over threaded state, thin route roots,
narrow hooks and colocated action modules,
interfacefor props and shared shapes, table-driven dispatch over condition ladders; desktop keeps routes insrc/app, atoms insrc/store, pure helpers insrc/lib. - Don't add core/plugin crossings: core never imports a specific bundled plugin, and plugins
use only the published plugin API (
hermes_cli/data/plugin_api.json,ctx). Existing crossings are baselined;scripts/boundary_lint.pyfails the test suite (notscripts/check) on new ones. - A new config key or slash command ships with its docs (
tests/ci/test_docs_drift.py). - Commits and PRs: rebase onto
mainbefore merging (a squash from a stale branch silently reverts newer fixes); 1–2 invariant tests per fix, proven red on the base. - Tests: always
scripts/run_tests.sh, never barepytest; behaviour contracts, never change-detectors or source-reading tests; host-specific behaviour is tested on that host with@pytest.mark.platforms(...), never by fakingsys.platform; tests never write to~/.hermes/.
| Area | Read | Covers |
|---|---|---|
run_agent.py, agent/ |
agent/AGENTS.md |
turn phases, message-flow invariants, compression, model/aux resolution, i18n |
cli.py, hermes_cli/ |
hermes_cli/AGENTS.md |
CLI mixins, slash registry, config system, skins, hermes update, profiles / multiplex |
gateway/ |
gateway/AGENTS.md |
adapters, message guards, streaming, notifications, token locks, § Profile scope |
plugins/platforms/ new adapter |
gateway/platforms/ADDING_A_PLATFORM.md |
step-by-step adapter guide |
tools/, toolsets.py, model_tools.py |
tools/AGENTS.md |
adding tools, registry, toolsets, delegation, session-scoped surface tools |
plugins/, hermes_cli/plugins*.py |
plugins/AGENTS.md |
plugin kinds, native compat contract, in-tree policy, boundary lint |
plugin-catalog/ |
plugin-catalog/README.md |
catalog admission rules (mirrored in the developer guide; keep identical) |
tui_gateway/, ui-tui/ |
tui_gateway/AGENTS.md |
process model, JSON-RPC transport, slash flow |
web/, hermes_cli/web_routers/ |
web/AGENTS.md |
dashboard embeds the real TUI |
apps/desktop/ |
apps/desktop/AGENTS.md, apps/desktop/src/AGENTS.md |
state authority, gateway backend contract, slash palette, Bot Mode |
skills/, optional-skills/, agent/curator*.py |
skills/AGENTS.md |
frontmatter, authoring standards, curator |
cron/, kanban |
cron/AGENTS.md |
scheduler invariants, job fields, kanban dispatcher |
tests/ |
tests/AGENTS.md |
runner, placement, OS markers, wine2e, banned test shapes |
pm/, pyproject.toml |
pm/AGENTS.md |
pinning policy, PM-owned environments, release-age rule |
hermes_platform/ |
hermes_platform/AGENTS.md |
host facts, resolvers |
acp_adapter/ |
website/docs/developer-guide/acp-internals.md |
ACP server for editors |
providers/ |
providers/README.md |
ProviderProfile registry and ABC |
PR workflow: CONTRIBUTING.md (maintainers: the hermes-agent-dev skill, not shipped here).