All plugins

dossier

Sourced, dated, refreshable dossiers on organisations and people in a professional capacity, framed for the meeting you actually have: interview, sales call, partnership, due diligence or supplier review. Every claim keeps a verbatim quote, a source URL, a publication date and a retrieval date; conflicting sources print as contested instead of being resolved, a figure the sources do not agree on is filed as unconfirmed rather than quoted, and a refresh reports only what changed since the last snapshot. Renders an A4 PDF, HTML, a chat card and a Desktop page, and ships an automation blueprint for scheduled refresh. Disclosure — makes outbound HTTP requests to the entity's own site and the pages it links to, to whatever hosts your configured Hermes web search backend returns, to api.github.com / github.com, and to www.sec.gov / data.sec.gov (SEC EDGAR); ABN Lookup (abr.business.gov.au) and Companies House (api.company-information.service.gov.uk) are contacted only when their credentials are configured; non-public addresses are refused; dossiers on people and organisations are stored under the plugin's data directory until you delete them with /dossier forget or the Desktop button.

FrazzlespriteToolsv0.2.0Updated 2 hours ago

About

Sourced, dated, refreshable dossiers on organisations and people in a professional capacity, framed for the meeting you actually have: interview, sales call, partnership, due diligence or supplier review. Every claim keeps a verbatim quote, a source URL, a publication date and a retrieval date; conflicting sources print as contested instead of being resolved, a figure the sources do not agree on is filed as unconfirmed rather than quoted, and a refresh reports only what changed since the last snapshot. Renders an A4 PDF, HTML, a chat card and a Desktop page, and ships an automation blueprint for scheduled refresh. Disclosure — makes outbound HTTP requests to the entity's own site and the pages it links to, to whatever hosts your configured Hermes web search backend returns, to api.github.com / github.com, and to www.sec.gov / data.sec.gov (SEC EDGAR); ABN Lookup (abr.business.gov.au) and Companies House (api.company-information.service.gov.uk) are contacted only when their credentials are configured; non-public addresses are refused; dossiers on people and organisations are stored under the plugin's data directory until you delete them with /dossier forget or the Desktop button.

Dossier

Sourced, dated, refreshable briefs on organisations and people in a professional public capacity, and what changed since you last looked.

Every sentence in a brief carries a source, a publication date and a retrieval date. If a claim has no evidence row, it does not print. A disagreement between sources is not resolved: it is printed as contested, with both sides and both dates.


Disclosure: what this plugin does with your data

Dossier makes outbound network requests to read public pages. It sends a descriptive User-Agent, respects robots.txt, and makes at most one request per host per second.

Hosts it contacts:

| Host | Why |
|---|---|
| The entity's own domain (and pages it links to, e.g. /about) | The organisation describing itself |
| Whatever host your configured Hermes web-search backend returns | Finding candidate pages, which are then fetched in full |
| api.github.com and github.com | Public organisation profile and repositories |
| www.sec.gov, data.sec.gov | SEC EDGAR: the published ticker file and filings per CIK (US, no key) |
| abr.business.gov.au | ABN Lookup (Australia, free GUID required) |
| api.company-information.service.gov.uk | Companies House (UK, free API key) |

It stores, in Hermes' per-plugin data directory (plugin-data/dossier/):

  • the entity record, including the purpose you gave when you created it;
  • claims, and for each claim the verbatim quote, source URL, publication date and retrieval date;
  • every generated brief, as a snapshot, so the next run can diff against it.

It does not read credentials, .env files, browser profiles or other plugins' data. It has no telemetry and makes no analytics calls. If a model is available to the session, extracted claims are model-assisted but pass the same quote-verification gate as the deterministic extractor.

/dossier forget <name>, or the Delete button in the Desktop, removes the entity, its claims, its evidence and every snapshot.


Permissible use

This section exists because the obvious version of this plugin is a privacy problem.

Allowed: organisations; individuals acting in a professional public capacity, where you supply a work context (a meeting, a hiring process, a partnership).

Not allowed, and not implemented:

  • no sensitive-category inference or storage: health, religion, sexual orientation, political affiliation, ethnicity, trade-union membership. These are refused at extraction, before a claim can reach the database, and no column exists to hold them;
  • no non-public personal data: home address, personal phone, family members, precise location;
  • no allegations presented as fact without a court record;
  • no scraping behind a login or against a site's terms;
  • no dossier on a private individual with no professional context.

Required: a stated purpose at creation, a retention period, and a delete that removes everything. Deleting is a slash command (/dossier forget <name>) and a Desktop button, never a tool, so a model cannot drop a dossier.

The brief's footer repeats this in one line. The policy tests are in tests/test_policy.py and fail the build if a sensitive term becomes a predicate or a column.


What it does

| Surface | What you get |
|---|---|
| /dossier <name> | Build or refresh a dossier, return a card |
| /dossier prep <name> [interview\|sales\|partnership\|diligence\|supplier] | Frame the brief for the meeting you actually have |
| /dossier show\|diff\|refresh\|forget\|watch <name> | The other verbs |
| dossier_build / dossier_show / dossier_diff / dossier_refresh / dossier_watch | The same five, as tools. Deleting is not one of them |
| Desktop → Dossiers | The list, the full brief, snapshot history, Refresh and Forget |
| Full brief | A4 PDF and HTML, two pages plus a numbered source appendix |
| Automation blueprint | A scheduled refresh users pick in /blueprint, no cron syntax |

Purpose and context

A brief written for a job interview should not read like one written for a sales call. dossier_build takes a purpose (your words, printed on the brief) and a context that changes the questions and the risk heading:

| Context | Risk heading | Questions are about |
|---|---|---|
| interview | What to be ready for | the team, the role, how success is measured |
| sales | Objections to expect | current spend, sign-off, what makes it urgent |
| partnership | Fit and friction | what they cannot build alone, who approves, the pilot |
| diligence | What would hurt | disagreeing sources, concentration, what stopped being said |
| supplier | Delivery risk | delivery exposure, who runs it, what changed |
| general | Risks | the gaps in the evidence |

If you pass only a purpose, the context is inferred from the words: "interview for the platform role" becomes interview, "first call with procurement" becomes sales. Both are stored on the entity, so a refresh keeps the framing, and the brief prints the purpose once in its header.

A person dossier always requires a stated purpose, and defaults to the interview context, because that is what a dossier on a person is almost always for.

A brief has: one line, why they matter to you (drawn from Hermes memory and labelled as memory), facts, recent items, contested or unconfirmed claims, risks, three questions to ask, and the sources.

Adapters: the official site (front page, the about pages it links, and the usual about paths when it links none), web search, news, GitHub, and company registry. SEC EDGAR runs out of the box and gives the registered name, the head office and filing dates, but no founding year: companies rarely tag one, and a first filing date is not a birth date. Companies House does give date_of_creation, and ABN Lookup adds the entity type and state when a GUID is configured. A registry record is a primary source, so it carries the top confidence band.

Person dossiers get the same pipeline with role claims instead of ceo, and they require a stated purpose: the professional context. Without one the build is refused.


Install

hermes plugins install owner/hermes-dossier    # from the catalog, once listed

Or for development, from a checkout:

# Windows
mklink /D "%LOCALAPPDATA%\hermes\plugins\dossier" "C:\Backpack\dossier-plugin"

Then enable it and restart the gateway so the dashboard routes mount:

hermes plugins enable dossier

The folder name must be dossier, because the Desktop loader requires the directory name to equal the plugin id.


Configuration

Declared in plugin.yaml, editable in Settings → Plugins → Dossier:

| Key | Default | Meaning |
|---|---|---|
| sources_per_brief | 12 | Hard cap on documents fetched for one brief |
| freshness_days | 90 | Window for the Recent section |
| retention_days | 90 | How long a dossier is kept before it is reported stale |
| allow_people | true | Set false to refuse dossiers on individuals |
| country | "" | Two-letter code for the registry adapter; empty asks every provider that has a credential |
| registry_contact | "" | Contact email SEC requires in the User-Agent. EDGAR is skipped until this is set |
| abn_lookup_guid | secret | Free ABN Lookup GUID (Australia). Optional |
| companies_house_api_key | secret | Companies House API key (United Kingdom). Optional |
| user_agent | Dossier/0.1 (+…) | Identifies the plugin in requests |

There are no Python dependencies at all: the fetcher is urllib, and the PDF writer is 250 lines in pdf.py. Nothing to install, nothing to audit.


How it is put together

name ──► resolve.py ──► one entity, or a question
             │
             ▼
         plan.py ──► queries and research questions
             │
             ▼
      adapters/*.py ──► normalised documents (url, title, published_at, text)
             │
             ▼
        extract.py ──► claims + verbatim quotes, policy-gated
             │
             ▼
        verify.py ──► confidence, dedupe, contested
             │
             ▼
       compose.py ──► sections, every bullet citing evidence
             │
             ▼
        render.py ──► A4 HTML + chat card
             │
             ▼
         store.py ──► snapshots + diff

Two deliberate design choices, both worth knowing before you change anything:

Composition is deterministic. compose.py applies templates to verified claims. It does not ask a model to write prose. That makes an unsourced sentence impossible by construction rather than by prompt discipline, and it means the same claim set always renders the same brief.

Extraction is gated, not trusted. The deterministic extractor captures the surrounding sentence as the quote. The optional model path returns a quote too, and both are rejected unless the quote is a verbatim substring of the fetched page text, at least 12 characters, inside the closed predicate set, and clear of every sensitive-category term. Rejections are counted and returned, never silently dropped.

Ambiguity is stopped, not averaged. Two same-name entities become a question, never a guess. And for a one-word name, where a homonym is likely, a third-party page is only allowed to contribute claims if it names the entity's own domain. The dropped sources are counted in the brief's notes, so the loss of coverage is visible rather than silent.


Deviations from the v1 spec, and why

| Spec | Built | Why |
|---|---|---|
| validate . --install-deps --ci | validate . --install-deps (or --json) | --ci is a doctor flag, not a validate flag in Hermes 0.23.0. |
| desktop/plugin.js only | desktop/plugin.js and dashboard/plugin_api.py | The Desktop renderer can only reach Python over HTTP. Without a backend route the page has nothing to call. |
| pre_llm_call injects brief content | Injects a one-line pointer to an existing dossier only | Claim text in a system turn goes stale silently. The hook says a dossier exists, when it was taken, and offers the diff. |
| Calendar-triggered refresh | Watchlist cadence plus the automation blueprint | There is no calendar API to read. The cadence and calendar_hint are stored and shown; scheduling is the blueprint's cron job, which is the supported seam. |


Development

python -m venv .venv
.venv\Scripts\pip install pytest pyyaml fastapi numpy edge-tts
.venv\Scripts\python -m pytest tests
hermes plugins doctor . --ci
hermes plugins validate . --install-deps

Two scripts do the unglamorous work of proving it outside the fixtures, and two rebuild the demo:

# one live brief against the real network, throwaway database
python scripts/live_check.py kalamari.club

# the installed plugin through Hermes' own storage, no model involved
set HERMES_HOME=%LOCALAPPDATA%\hermes
python scripts/harness_check.py kalamari.club https://kalamari.club

# the demo case offline: a contradiction, then a leadership change
python scripts/demo_contradiction.py

# replay the captured Valve set through the real pipeline, no network
python scripts/demo_valve.py

# rebuild the demo (Pillow, numpy, imageio-ffmpeg, edge-tts; dev only)
python scripts/make_briefs.py         # briefs.json and 2x screenshots
python scripts/make_video_demo.py     # docs/dossier-demo.mp4

The demo scripts are developer tooling and are not loaded by the plugin. Two that reached into a Hermes internal for the search backend were removed, so hermes plugins validate now reports no private core imports at all; the plugin itself only uses ctx.dispatch_tool.

Demo

docs/dossier-demo.mp4: 34 seconds. Hook, a real terminal, dated news arriving, and the same Valve evidence read twice, once for an interview and once for a sales call. Score and voiceover are synthesised at build time, so there is nothing to license. Every figure, date and question comes from a replay of the captured sources, and the brief frames are screenshots of the HTML and PDF that run produced. The caution on screen is the brief's own: one source gives three headcounts, so the brief refuses to hand you one.

Three things that will save you an afternoon:

  1. The plugin root is a package. Hermes loads it with spec_from_file_location(..., submodule_search_locations=[plugin_dir]), so internal imports are relative. A flat import db fails at load time, not at test time.
  2. Run pytest as python -m pytest tests. tests/pytest.ini sets the rootdir to tests on purpose; without it the collector climbs out of the repo and tries to import the root __init__.py as a top-level module.
  3. Do not touch ctx.llm inside register(). It is a property, and the validation probe's recording context does not stub properties, so register() raises and the capability probe check fails. Model access belongs in handlers.

Licence

MIT. See LICENSE.

Everything you should know before you walk in, sourced and dated.

README from the reviewed commit 00f7abe; it updates when the author re-pins.

More by Frazzlesprite

3 plugins by Frazzlesprite in the catalog.

Developer Mode

Explore built-in Hermes toolsets and actions.

HermesAgent

Developer?

Create a Hermes extension for Desktop or agent workflows.

Developer Section