Hermes health information agent — implementation plan
A Hermes Agent (Nous Research) configuration that answers questions about current treatment approaches for medical conditions — including HIV/AIDS, cancer and myopathies — finds licensed specialists near the user, and surfaces mental health support when it's needed.
Stack: Hermes Agent + Nebius (GLM) + Tavily + four public government APIs.
The one design principle
The model never states a medical fact, provider name, or address from its own memory. Every claim traces to a tool result from the current turn. GLM is the synthesiser and writer, not the source of truth.
This matters more here than in most agents. A hallucinated "new treatment" for a stigmatised condition, or a clinic address that doesn't exist, is a real harm to a frightened person — not a bad user experience.
Everything else in this document exists to enforce that principle.
Architecture
The safety gate runs in your ingestion code, ahead of Hermes — not as a tool
the agent chooses to call. The three research branches run in parallel via
Hermes's delegate_task subagents, then converge on a synthesis step that
cites every claim and tags every treatment with its evidence stage.
| Stage | Implementation |
|---|---|
| Safety gate | scripts/safety_gate.py — crisis short-circuits, agent never runs |
| Agent loop | Hermes + GLM via Nebius, low temperature, prompts/system_prompt.md |
| Disease research | skills/disease-research/ → medical_search.py, clinical_trials.py |
| Provider lookup | skills/provider-lookup/ → provider_lookup.py |
| Mental health | skills/mental-health-resources/ → mental_health.py |
What Hermes gives you vs. what you build
Already in Hermes — use as-is
| Capability | How it helps |
|---|---|
| Tavily integration | Search backend, already wired in |
execute_code / terminal |
Runs the scripts/ helpers, no tool registration needed |
delegate_task |
Parallel subagents for the three branches |
| Skills system | Packages each branch as a reusable, reliably-triggered unit |
cronjob |
Nightly treatment-cache refresh |
| MCP integration | Alternative to scripts if you'd rather wrap the APIs in a server |
| Messaging gateway | Telegram / Discord / Slack / WhatsApp / Signal delivery |
You build
| Piece | Why |
|---|---|
scripts/medical_search.py |
Built-in web_search has no source allowlist |
scripts/clinical_trials.py |
ClinicalTrials.gov isn't a native tool |
scripts/provider_lookup.py |
NPI Registry isn't a native tool |
scripts/safety_gate.py |
Must run outside the agent loop — see below |
| Custom API layer | Only if you want a web/mobile UI instead of a chat platform |
Hermes defaults you must override
- Memory is on and aggressive. Hermes persists conversation state, builds a user profile across sessions, and auto-generates skills from past work. For HIV status and mental health queries that default is actively harmful. See Privacy.
- ~85 tools ship enabled. Browser control, desktop GUI, Spotify,
computer_use. Disable everything this agent doesn't need — smaller attack surface, and the planner stops getting distracted. - Safety can't be a tool. Hermes is an autonomous planner deciding what to call. "Always check for crisis language first" is not something you delegate to a planner. It runs in your ingestion code.
Repository layout
hermes-medical-assistant/
├── README.md
├── .env.example
├── .gitignore # blocks .env, cache/, logs — see Privacy
├── docs/
│ └── architecture.svg
├── config/
│ └── hermes-config.yaml.example # provider, toolset allowlist, memory scoping
├── prompts/
│ └── system_prompt.md # grounding rules, boundaries, tone
├── skills/
│ ├── disease-research/SKILL.md
│ ├── provider-lookup/SKILL.md
│ └── mental-health-resources/SKILL.md
├── scripts/
│ ├── sources.py # allowlists, taxonomy map, crisis lines
│ ├── medical_search.py # Tavily, domain-restricted
│ ├── clinical_trials.py # ClinicalTrials.gov API v2
│ ├── provider_lookup.py # NPI Registry + Places fallback
│ ├── mental_health.py # crisis resources + facilities
│ └── safety_gate.py # pre-agent crisis screening
└── evals/
└── cases.jsonl # 15 adversarial cases, run before shipping
Data sources
| Source | Endpoint | Key? | Used for |
|---|---|---|---|
| Tavily | api.tavily.com/search |
Yes | Guidelines, recent developments — locked to a medical allowlist |
| ClinicalTrials.gov v2 | clinicaltrials.gov/api/v2/studies |
No | Active trials, structured phase/status/location |
| NPI Registry (NPPES) | npiregistry.cms.hhs.gov/api/?version=2.1 |
No | US licensed providers by specialty + city/state/ZIP |
| Google Places | Text Search | Yes | Non-US geo lookup — business directory, not licensure-verified |
| SAMHSA FindTreatment | findtreatment.gov |
No | US mental health facilities — verify current API contract before enabling |
Two things to know about the provider APIs: NPI Registry is authoritative for
licensure but has no lat/long radius search — city, state or postal code
only, and state alone is rejected. Google Places does true geo-radius and works
internationally but proves nothing about credentials, so every Places result is
returned with verified_licensure: false and must be labelled as such in
output.
Allowlists live in scripts/sources.py and vary by condition category — HIV
queries add hiv.gov and unaids.org, oncology adds cancer.gov and nccn.org,
and so on. High-ranking SEO health sites are explicitly blocked.
Build order
Ship in this sequence. Each phase is independently testable.
Phase 1 — Grounded disease research (2–3 days)
hermes model→ select Nebius, set GLM. Confirm tool-calling works with a trivial call before anything else; format mismatches between the model and the framework are the most common silent failure.- Drop in
scripts/sources.pyandmedical_search.py. Test the allowlist holds — search something that would normally return WebMD and confirm it doesn't. - Add
clinical_trials.py. Verify NCT numbers resolve to real studies. - Install
skills/disease-research/. Loadprompts/system_prompt.mdas a context file. - Gate: run the
grounding-*andhiv-*eval cases. Zero uncited claims.
Phase 2 — Safety gate (1–2 days)
- Wire
safety_gate.pyinto your ingestion path, ahead of Hermes. - Confirm
short_circuitgenuinely prevents the agent loop from running — not just prepends a warning to the output. - Tune keyword patterns against real phrasing. Bias toward false positives.
- Gate: all
safety-*cases pass, includingsafety-03(research framing must not trip the gate).
Phase 3 — Provider lookup (2–3 days)
- Add
provider_lookup.py. Test both the US and non-US paths. - Install
skills/provider-lookup/. - Confirm the model asks for location rather than guessing, and never emits an address that wasn't in a tool result.
- Gate:
location-*,cancer-02,grounding-02pass.
Phase 4 — Mental health (1–2 days)
- Add
mental_health.pyand its skill. Verify every hardcoded helpline by calling it. - Test the proactive offer fires once after a serious-diagnosis response and doesn't repeat when declined.
Phase 5 — Hardening (3–5 days)
- Lock the toolset via
hermes tools. - Apply the memory scoping in
config/hermes-config.yaml.exampleand enforce it in your handler — don't trust config alone. - Add the caching layer.
- Run the full eval set. Then run it again with 20+ paraphrases of each case, because single-phrasing passes hide brittleness.
- Have a clinician review sample outputs for the three headline conditions before any real user sees it.
Caching
Treatment landscapes change weekly, not hourly. Cache the search + trials + synthesis result per condition with a 24–72h TTL. Provider lookups are location-specific and should stay live.
This cuts Tavily spend and GLM tokens substantially, and it makes the common path fast. The cron job in the config example refreshes the headline conditions nightly.
Privacy and compliance
The single largest risk in this project is not a wrong answer. It's a leak.
What you're handling: HIV status, cancer diagnoses, mental health queries, and locations tied to all three. This is among the most sensitive personal data that exists.
Concrete requirements:
- Scope Hermes memory. Disable auto-skill generation and session summaries. Health conditions, diagnoses and health-linked locations must not reach persistent storage. Enforce this in your own handler, not only in config.
- Encrypt at rest, short retention, tight access controls on whatever you do keep.
- GDPR: health data is special-category data. You need an explicit legal basis and, in most cases, explicit consent. Not a checkbox buried in terms.
- HIPAA: as a standalone consumer app you're likely not a covered entity — but the moment you partner with a clinic or provider, you probably are. Build to that standard now; retrofitting is expensive.
- Avoid the medical-device line. Copy and UI must not imply diagnostic capability. "Understand your options" is fine; "find out what you have" moves you toward software-as-a-medical-device regulation in both the US and EU.
- Crisis logging. Conversations that trip the safety gate should not be logged at all beyond an anonymous counter.
Not legal advice — get a lawyer who does health tech before launch, especially for GDPR and any clinic partnership.
Evals
evals/cases.jsonl covers 15 cases across six failure modes: grounding,
evidence staging, provider fabrication, diagnosis boundaries, crisis handling,
and privacy. Each has explicit expect and fail_if criteria.
Run them before every release, not just the first one. The failure that matters
most is evidence-stage blurring — a phase-I candidate presented as an
available treatment — because it reads perfectly fluent and only a domain
expert catches it. myopathy-01 targets this specifically.
Also worth building: a paraphrase harness. A single-phrasing pass tells you very little about whether the behaviour is robust.
Known gaps
- SAMHSA endpoint unverified.
FINDTREATMENT_ENABLEDis off by default. Confirm the current API contract at findtreatment.gov before turning it on; it falls back to the provider-lookup path meanwhile. - Non-US provider coverage is weak. No national licensure registry equivalent to NPPES is wired in. Country-specific directories (NHS, Ukrainian MoH, etc.) would each need their own integration.
- English-only allowlists. Serving Ukrainian or other non-English users well means adding local-language authoritative sources, not just translating output.
- No insurance data. The agent can't tell anyone whether a provider takes their coverage, which is often the first question that actually matters.
- GLM tool-call format. Confirm the exact schema Hermes emits matches what your GLM build was tuned for. Mismatches fail silently — the model just stops calling tools and starts answering from memory, which is precisely the failure this whole design exists to prevent. Test this explicitly.
Quick start
git clone https://github.com/Kolezhniuk/hermes-medical-assistant.git
cd hermes-medical-assistant
cp .env.example .env # fill in NEBIUS_API_KEY, TAVILY_API_KEY
cp config/hermes-config.yaml.example ~/.hermes/config.yaml
# verify the data sources independently of the agent
python scripts/clinical_trials.py --condition "HIV" --max 3
python scripts/provider_lookup.py --condition hiv --city Boston --state MA
python scripts/safety_gate.py "I just got diagnosed and I'm scared" --country US
# then start the agent
hermes tools # disable everything not in the allowlist
hermes # first conversation
This plan describes an information tool, not a medical device. It does not diagnose, and it is not a substitute for care from a licensed clinician.
Built With
- hermes
- nebius
- talivy