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

Pipeline: user prompt to safety gate to intent router to three parallel branches to grounded synthesis to final response

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

  1. 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.
  2. ~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.
  3. 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)

  1. 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.
  2. Drop in scripts/sources.py and medical_search.py. Test the allowlist holds — search something that would normally return WebMD and confirm it doesn't.
  3. Add clinical_trials.py. Verify NCT numbers resolve to real studies.
  4. Install skills/disease-research/. Load prompts/system_prompt.md as a context file.
  5. Gate: run the grounding-* and hiv-* eval cases. Zero uncited claims.

Phase 2 — Safety gate (1–2 days)

  1. Wire safety_gate.py into your ingestion path, ahead of Hermes.
  2. Confirm short_circuit genuinely prevents the agent loop from running — not just prepends a warning to the output.
  3. Tune keyword patterns against real phrasing. Bias toward false positives.
  4. Gate: all safety-* cases pass, including safety-03 (research framing must not trip the gate).

Phase 3 — Provider lookup (2–3 days)

  1. Add provider_lookup.py. Test both the US and non-US paths.
  2. Install skills/provider-lookup/.
  3. Confirm the model asks for location rather than guessing, and never emits an address that wasn't in a tool result.
  4. Gate: location-*, cancer-02, grounding-02 pass.

Phase 4 — Mental health (1–2 days)

  1. Add mental_health.py and its skill. Verify every hardcoded helpline by calling it.
  2. Test the proactive offer fires once after a serious-diagnosis response and doesn't repeat when declined.

Phase 5 — Hardening (3–5 days)

  1. Lock the toolset via hermes tools.
  2. Apply the memory scoping in config/hermes-config.yaml.example and enforce it in your handler — don't trust config alone.
  3. Add the caching layer.
  4. Run the full eval set. Then run it again with 20+ paraphrases of each case, because single-phrasing passes hide brittleness.
  5. 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_ENABLED is 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
Share this project:

Updates

Submission history