Skip to content

emit

Agent configs generated from AGENT_SURFACES, and drift detected against it.

The four attestation-<surface> entries in ~/.hermes/config.yaml were typed by hand. install.py writes only the single full-surface attestation entry and its doctor matched on the substring "attestation", so a config with the full server and zero surfaces reported ok -- the same false-clean the scheduler check had before it learned to look for a second entry.

Two copies of one fact with no check between them is the whole problem, so there is exactly one generator here and every consumer calls it, install.py included. Emitting from two places to keep two files in sync would be an embarrassing bug to reintroduce inside its own fix.

Nothing here overwrites. A difference between generated and on-disk is reported with a diff and left alone; writing happens only on an explicit attest emit --write. A checksum would turn a user's deliberate edit into a warning they cannot act on, and silently rewriting it is worse. This is the contract _write_refresh_script already uses ("missing or stale"), so it is this repo's existing convention rather than a new one.

Finding dataclass

One disagreement between the generated config and the one on disk.

Surface dataclass

One agent's remit: what it may see, and why it is its own session.

check_hermes(servers, root)

Compare an on-disk mcp_servers mapping against what this table says.

Compares against the generator, not against a substring. orphaned is the one a substring check can never find and the reason this exists: an entry naming a surface that no longer exists still launches, and register_all raises on it -- loudly, but at the user's next tool call rather than at install time.

claude_agents(root)

.claude/agents/<name>.md bodies, one per surface.

Same table, different runtime. The tool list is the surface's prefixes rather than an enumeration: a new tool in a namespace joins its agent without anyone remembering to add it here.

hermes_servers(root)

The mcp_servers fragment: one entry per surface.

enabled: false is correct and stays. A surface is chosen at launch by a person -- that is the finding the split rests on -- and five servers all enabled would put every tool back into one session.

parse_config_dump(text)

hermes config get mcp_servers output, as a mapping.

Not a YAML parser and not trying to be -- the same call _config_shape in ledger_adapters/generic.py makes, and for the same reason: this input is machine-generated, two levels deep, and unquoted, and taking on a parser dependency for it is how this codebase got burned before.

hermes mcp list is the obvious alternative and is the wrong one: it truncates args to a fixed column width, so every entry would look stale.