Hermes Agent · Internal Audit

LLM Prompt Surface — File Audit

Every .md and context file Hermes Agent loads on a new conversation, cron tick, or other activity — full absolute paths, loader functions, and trigger conditions. All citations anchored to file:line in /Users/morsy/dev/hermes at commit 2f7a3274c4.

Light theme Audit Verified 2026-09-08 HEAD 2f7a3274c4

Summary by the numbers

Hermes core code loads a small, well-defined set of context files at every prompt build. The big surprise: most workspace files that Morsy's AGENTS.md tells the agent to read are not loaded by Hermes — they're a self-prompting convention.

2
Stable identity
2
Memory files
8
Project-context patterns
9
Skill-index patterns
5
Skill-body patterns
5
Workspace files NOT auto-loaded
The 3-layer prompt assembly. build_system_prompt() at agent/system_prompt.py:1008 calls build_system_prompt_parts() which assembles three tiers: stable (identity + communication style + tool guidance, byte-stable for the session), context (project-context files + SOUL.md), volatile (skills index, memory, USER profile, plugins, timestamp). Each tier is injected at a distinct cache tier to maximize prefix-cache reuse.

Files auto-loaded by Hermes core

Every row below is anchored to file:line in the source. HERMES_HOME resolves per-profile (default → /Users/morsy/.hermes/). <cwd> / <git-root> are computed at session start from the active working directory.

A Stable identity — every session
# Full path Loaded by When loaded
A1 /Users/morsy/.hermes/SOUL.md agent/prompt_builder.py:2196 load_soul_md() → called from system_prompt.py:451 Every sessionWhen load_soul_identity=True OR skip_context_files=False. Confirmed exists (3,584 bytes).
A2 /Users/morsy/.hermes/agent-communication-style.md agent/prompt_builder.py:2229 load_communication_style_md() → called from system_prompt.py:465 Every sessionSingle source of truth for response format (One-liner / Bullets / Cards). Confirmed exists (3,333 bytes).
B Project context — cwd/git-root dependent, first-match-wins
Priority-based, not merged. build_context_files_prompt() tries .hermes.mdAGENTS.md chain → CLAUDE.md.cursorrules. Only one project-context type is loaded per session (prompt_builder.py:2465–2470).
# Full path pattern Loaded by When loaded
B1 <cwd>/.hermes.md agent/prompt_builder.py:104 _find_hermes_md()_load_hermes_md() at line 2244 If cwd or any parent up to git root contains this fileWalks cwd → git root, returns first match.
B2 <cwd>/HERMES.md Same loader as B1 _HERMES_MD_NAMES = (".hermes.md", "HERMES.md") Same as B1; uppercase fallback
B3 <git-root>/AGENTS.override.md agent/prompt_builder.py:2297 _load_agents_md(); chain from line 2270 If no .hermes.md/HERMES.md found AND chain dir contains this fileFirst-name-wins per directory. Personal, typically gitignored.
B4 <git-root>/AGENTS.md Same loader, line 2315 Same as B3; lower-priority name
B5 <git-root>/agents.md Same loader, line 2315 Same as B3; lowercase fallback
B6 <intermediate-dir>/AGENTS.override.md
<intermediate-dir>/AGENTS.md
<intermediate-dir>/agents.md
Same loader, line 2314 Loop walks chain dirs root → cwd One row per dir between git-root and cwdMerged into one section. Identical content across chain dirs is deduped (prompt_builder.py:2326).
B7 <cwd>/CLAUDE.md agent/prompt_builder.py:2355 _load_claude_md() Only if no .hermes.md AND no AGENTS.md chain AND cwd contains thisCwd only, no parent walk.
B8 <cwd>/claude.md Same loader, line 2357 Same as B7; lowercase fallback
B9 <cwd>/.cursorrules agent/prompt_builder.py:2374 _load_cursorrules() Only if none of B1–B8 matchedCwd only.
B10 <cwd>/.cursor/rules/*.mdc
(one per .mdc file; sorted)
Same loader, line 2389 cursor_rules_dir.glob("*.mdc") Same as B9; sorted alphabeticallyAll matched files included.
C Memory — every session, when enabled
# Full path Loaded by When loaded
C1 /Users/morsy/.hermes/memories/MEMORY.md tools/memory_tool.py:247 load_from_disk() at line 227 Every sessionInjected as volatile-tier MEMORY block at system_prompt.py:902. Only when _memory_enabled=True. Confirmed exists (2,034 bytes).
C2 /Users/morsy/.hermes/memories/USER.md tools/memory_tool.py:248 Every sessionInjected at system_prompt.py:907. Only when _user_profile_enabled=True. Confirmed exists (1,367 bytes).
D Skills index — every session, description-only in prompt
Index vs body. The system prompt contains names + descriptions for each visible skill. The full SKILL.md body is fetched on demand via the skill_view(name) tool — never auto-loaded.
# Full path pattern Loaded by When loaded
D1 /Users/morsy/.hermes/skills/**/SKILL.md
(one per skill)
agent/prompt_builder.py:1876 iter_skill_index_files() at skill_utils.py:1203 Every sessionCold path (line 1876) or snapshot path (line 1853).
D2 /Users/morsy/.hermes/skills/**/DESCRIPTION.md
(one per category subdir)
agent/prompt_builder.py:1971 Every sessionCategory-level description lines.
D3 /Users/morsy/.hermes/skills/_snapshot.json
(pre-parsed cache)
agent/prompt_builder.py:1579 _load_skills_snapshot() Every sessionWhen present and valid, metadata is read from this file instead of re-parsing disk. Skipped on miss → cold path scans D1 + D2.
D4 /Users/morsy/.hermes/skills/_org/<active_org>/**/SKILL.md
(active org mirror only)
agent/skill_utils.py:1220–1228 Token-gated by .active_org marker Every sessionSame description-only injection as D1. Org mirror subset only.
D5 /Users/morsy/.hermes/skills/_org/<active_org>/**/DESCRIPTION.md Same loader as D4 Same as D4
D6 <skills.external_dirs[i]>/**/SKILL.md
(per external dir, expanded ~/${VAR})
agent/prompt_builder.py:2003 Dirs from get_external_skills_dirs() at skill_utils.py:533 Every sessionCold-path scan, no snapshot cache. Deduped against local.
D7 <skills.external_dirs[i]>/**/DESCRIPTION.md agent/prompt_builder.py:2029 Same as D6
D8 <project-root>/.hermes/skills/**/SKILL.md
(trusted only)
agent/skill_utils.py:895 iter_project_skill_files() When cwd is in a git repo whose root is in skills.trusted_project_dirsTrust gate at skill_utils.py:776. Highest precedence — shadows same-name local/external skills.
D9 <project-root>/.agents/skills/**/SKILL.md
(cross-tool convention; trusted only)
Same loader as D8 Same trigger as D8
F Cron-specific — every cron tick, when set
# Full path pattern Loaded by When loaded
F1 /Users/morsy/.hermes/cron/output/<source_job_id>/*.md
(latest by mtime; one per context_from job)
cron/scheduler.py:4662 job_output_dir.glob("*.md"), sorted mtime desc Each cron tickWhen the job's context_from references an upstream job whose output dir contains *.md. Truncated to 8,000 chars.
F2 <stored job["prompt"]>
(user-supplied cron prompt string, not a file)
cron/scheduler.py:4588 Every cron tick

Files loaded on demand

These files are NOT injected at session start. They're fetched and injected into the user message of the specific turn that triggers them.

E Skill bodies — fetched when skill_view(name) is called
# Full path pattern Loaded by When loaded
E1 /Users/morsy/.hermes/skills/<category>/<name>/SKILL.md
(full body)
tools/skills_tool.py:1086 skill_view(name) When the model calls skill_view, OR a cron job has skills: [name] (cron/scheduler.py:4773)Injects the full body into the user message of that turn only.
E2 …/skills/<cat>/<name>/references/* Same loader skill_view(name, file_path="…") When the model explicitly opens a referenced file
E3 …/skills/<cat>/<name>/templates/* Same as E2 Same trigger as E2
E4 …/skills/<cat>/<name>/scripts/* Same as E2 Same trigger as E2
E5 …/skills/<cat>/<name>/assets/* Same as E2 Same trigger as E2

Plugin-driven / external-memory blocks

Variable. The system prompt also carries blocks from memory providers (Honcho, Mem0, Hindsight, RetainDB, Supermemory, ByteRover, OpenViking) and from registered plugins. These are not file paths — they are plugin-defined content. Hooks:
  • agent/system_prompt.py:915–926 calls agent._memory_manager.build_system_prompt()
  • agent/system_prompt.py:931 calls _frozen_plugin_prompt_sections(agent)

Files NOT loaded by Hermes core

These files exist in ~/.hermes/workspaces/<agent>/ and the workspace AGENTS.md tells the agent to read them manually. Hermes has no loader for them — confirmed by AST scan of every load_*, read_text, glob, iterdir, os.walk call in the prompt-assembly path.

Workspace convention, not Hermes feature. The "read SOUL.md / IDENTITY.md / MEMORY.md / USER.md / TOOLS.md / HEARTBEAT.md / AGENTS.md / memory/YYYY-MM-DD.md on every session" instruction is a self-prompting convention. The agent uses read_file to fetch them after its own AGENTS.md instructs it to.
# Full path pattern What it is How the agent reads it
N1 /Users/morsy/.hermes/workspaces/<agent>/AGENTS.md Instructions to the agent itself Agent reads it manually via read_fileHermes loads the cwd-rooted AGENTS.md chain only, not the workspace one.
N2 /Users/morsy/.hermes/workspaces/<agent>/SOUL.md Same file as HERMES_HOME/SOUL.md for default profile Agent reads it manuallyHermes auto-loads HERMES_HOME/SOUL.md, which is the same physical file for the default profile.
N3 /Users/morsy/.hermes/workspaces/<agent>/IDENTITY.md No core loader exists Agent reads it manuallyOnly referenced in plugins/memory/honcho/cli.py:1615 as a workspace convention listing.
N4 /Users/morsy/.hermes/workspaces/<agent>/MEMORY.md No core loader exists Agent reads it manuallyOnly HERMES_HOME/memories/MEMORY.md is auto-injected.
N5 /Users/morsy/.hermes/workspaces/<agent>/USER.md No core loader exists Agent reads it manuallyOnly HERMES_HOME/memories/USER.md is auto-injected.
N6 /Users/morsy/.hermes/workspaces/<agent>/TOOLS.md No core loader exists Agent reads it manually
N7 /Users/morsy/.hermes/workspaces/<agent>/HEARTBEAT.md Read on heartbeat poll Agent reads it manually when it receives a heartbeat pollWorkspace AGENTS.md instructs this behavior, not Hermes core.
N8 /Users/morsy/.hermes/workspaces/<agent>/memory/YYYY-MM-DD.md
(today + yesterday)
No core loader exists Agent reads them manually
N9 /Users/morsy/.hermes/workspaces/<agent>/BOOTSTRAP.md First-run convention Agent reads it once on first run, then deletes it

Key findings

What this audit means in practice for clawy (and any agent running under the default profile).

Workspace AGENTS.md ≠ Hermes AGENTS.md loader
The workspace ~/.hermes/workspaces/clawy/AGENTS.md is NOT auto-loaded by Hermes. Only the cwd-rooted chain (_agents_md_directory_chain()) is. The workspace AGENTS.md is a self-prompting convention.
MEMORY.md / USER.md location matters
MEMORY.md / USER.md come from ~/.hermes/memories/, NOT from ~/.hermes/workspaces/<agent>/. Both files exist for clawy but only the memories/ copy is auto-injected.
No loader for IDENTITY / TOOLS / HEARTBEAT / BOOTSTRAP
Confirmed by AST scan: only plugins/memory/honcho/cli.py:1615 references those filenames — and that's a CLI helper listing workspace conventions, not a load path.
Project-context priority = first-match-wins
.hermes.md > AGENTS.md chain > CLAUDE.md > .cursorrules. Only one is loaded per session. If a git root has .hermes.md and cwd has .cursorrules, only the .hermes.md lands.
Skill bodies are on-demand
System prompt only embeds skill names + descriptions. Full SKILL.md body fetched via skill_view(name) or cron skills: [name]. Keeps the stable prefix cacheable.
Skills index has a disk cache
~/.hermes/skills/_snapshot.json — on warm cache, SKILL.md / DESCRIPTION.md files are NOT re-read at session start. Manifest mtime/size invalidates the snapshot.
Install-tree cwd fallback is gated
A cwd fallback that lands inside the Hermes install tree (_is_install_tree()) skips all four context loaders — prevents dev/hermes/AGENTS.md from auto-loading as authoritative project context (#64590).
channel_prompts is config-inline, not a file
Resolved at message receipt by gateway/platforms/base.py:2914. The value is a literal string in ~/.hermes/config.yaml, ephemeral, never persisted to transcripts.

Citations index

All paths above are anchored in the following source files. Each row is (file:line → loader function → what it loads).

agent/prompt_builder.py:104       _find_hermes_md                .hermes.md / HERMES.md walk
agent/prompt_builder.py:150       DEFAULT_AGENT_IDENTITY          fallback identity string
agent/prompt_builder.py:1579      _load_skills_snapshot           _snapshot.json
agent/prompt_builder.py:1676      _parse_skill_file               individual SKILL.md
agent/prompt_builder.py:1820+     build_skills_system_prompt      orchestration
agent/prompt_builder.py:1876      cold-path SKILL.md scan
agent/prompt_builder.py:1971      category DESCRIPTION.md
agent/prompt_builder.py:2003      external SKILL.md scan
agent/prompt_builder.py:2029      external DESCRIPTION.md
agent/prompt_builder.py:2173      load_soul_md                    SOUL.md
agent/prompt_builder.py:2214      load_communication_style_md     agent-communication-style.md
agent/prompt_builder.py:2244      _load_hermes_md                 .hermes.md / HERMES.md body
agent/prompt_builder.py:2270      _agents_md_directory_chain      AGENTS.md chain dirs
agent/prompt_builder.py:2297      _load_agents_md                 AGENTS.md / agents.md / AGENTS.override.md
agent/prompt_builder.py:2355      _load_claude_md                 CLAUDE.md / claude.md
agent/prompt_builder.py:2374      _load_cursorrules               .cursorrules + .cursor/rules/*.mdc
agent/prompt_builder.py:2407      build_context_files_prompt      full project-context orchestration

agent/skill_utils.py:533          get_external_skills_dirs        skills.external_dirs
agent/skill_utils.py:616          get_all_skills_dirs             local + external
agent/skill_utils.py:762          get_project_skills_dirs         trusted project roots
agent/skill_utils.py:895          iter_project_skill_files        /.hermes/skills + .agents/skills
agent/skill_utils.py:1203         iter_skill_index_files          generic walker

agent/system_prompt.py:406        build_system_prompt_parts       3-tier assembly
agent/system_prompt.py:447–454    SOUL.md loader hook
agent/system_prompt.py:458        DEFAULT_AGENT_IDENTITY fallback
agent/system_prompt.py:465–467    communication-style loader hook
agent/system_prompt.py:900–909    MEMORY/USER block loader hook
agent/system_prompt.py:915–926    external memory-provider block
agent/system_prompt.py:1008       build_system_prompt             full assembly

tools/memory_tool.py:64           get_memory_dir                  memories dir
tools/memory_tool.py:227          load_from_disk                  MEMORY.md + USER.md
tools/memory_tool.py:247          MEMORY.md read
tools/memory_tool.py:248          USER.md read

tools/skills_tool.py:1086         skill_view                      full SKILL.md body on demand

cron/scheduler.py:4570            _assemble_cron_prompt           cron-specific user msg
cron/scheduler.py:4588            job["prompt"]
cron/scheduler.py:4602–4627       pre-run script output
cron/scheduler.py:4662            upstream-job *.md glob
cron/scheduler.py:4773            skill_view(name) for cron skills

gateway/platforms/base.py:2914    resolve_channel_prompt          channel_prompts config lookup

hermes_constants.py:114           get_hermes_home                 HERMES_HOME root
hermes_constants.py:1658          get_skills_dir                  HERMES_HOME/skills

Methodology

Reproducible steps. ~5 minutes on /Users/morsy/dev/hermes.

  1. Resolved the active source tree: /Users/morsy/dev/hermes @ 2f7a3274c4 on main (with reapply v0.20.6 local changes)
  2. Located the central loader: agent/prompt_builder.py + agent/system_prompt.py
  3. Read every loader function: load_soul_md, load_communication_style_md, _load_hermes_md, _load_agents_md, _load_claude_md, _load_cursorrules, build_context_files_prompt, build_skills_system_prompt
  4. Traced call sites: build_system_prompt_parts() at agent/system_prompt.py:406 → 3-tier assembly
  5. Memory path: tools/memory_tool.py:load_from_disk() at line 227
  6. Skills index path: agent/skill_utils.py:iter_skill_index_files() at line 1203 + prompt_builder.py scanners
  7. Cron-specific: cron/scheduler.py:_assemble_cron_prompt() at line 4570
  8. Channel prompts: gateway/platforms/base.py:resolve_channel_prompt() at line 2914
  9. Negative-result verification: AST scan for MEMORY.md / USER.md / IDENTITY.md / TOOLS.md / HEARTBEAT.md / BOOTSTRAP.md across all *.py (excluding tests/) → only plugins/memory/honcho/cli.py:1615 and migration scripts reference the no-loader filenames, confirming they are NOT loaded at runtime.