All plugins

thomas

Fine-tune a small calibrated encoder classifier on Modal and rate it with gonogo. Every GPU launch goes through the human approval gate with the exact config shown; data checks and evaluation run locally for free. Disclosure — thomas_encoder_train spends the user's Modal GPU credits and uploads the chosen training JSONL rows to Modal after the human approval gate; the base model is downloaded from Hugging Face on the Modal side. Training and inference run in a separate Python interpreter the user configures via THOMAS_PYTHON (with the author's thomas package, torch and modal installed); the Hermes venv only gets gonogo-eval. Runs and artifacts are written under HERMES_HOME/thomas/runs; the eval tool writes report files to the path the model supplies.

keppyToolsv0.1.3Updated 12 days ago

About

Fine-tune a small calibrated encoder classifier on Modal and rate it with gonogo. Every GPU launch goes through the human approval gate with the exact config shown; data checks and evaluation run locally for free. Disclosure — thomas_encoder_train spends the user's Modal GPU credits and uploads the chosen training JSONL rows to Modal after the human approval gate; the base model is downloaded from Hugging Face on the Modal side. Training and inference run in a separate Python interpreter the user configures via THOMAS_PYTHON (with the author's thomas package, torch and modal installed); the Hermes venv only gets gonogo-eval. Runs and artifacts are written under HERMES_HOME/thomas/runs; the eval tool writes report files to the path the model supplies.

thomas — a Hermes plugin

Train a small calibrated classifier on a Modal GPU and get a gonogo verdict on
it, without leaving the conversation. Launches through thomas_encoder_train
use Hermes' human approval gate; direct Python, terminal and Modal commands
are outside this hook's scope. Checking data and CPU eval are local and free.

The ecosystem this belongs to

This plugin is the in-session half of a small ecosystem that takes a task
from which model? to ship it or not:

your task ──► evalroute ─ choose a (model, effort) arm;
                │         measured where available, priors marked
                ▼
your cases ──► thomas ── train on labels (encoder) or score_text (RL);
                │         evaluate on untouched cases with gonogo
                ▼
              gonogo ── ship it, test a threshold on fresh cases, or walk away
  • thomas — the library behind this
    plugin. RL shares score_text across training and held-out evaluation;
    encoder SFT instead learns labels and needs a separate held-out evaluation.
  • gonogo — the decision layer, and the
    root of the map. Any agent, your real cases, a target; the verdict comes
    with the interval behind it.
  • evalroute — the
    routing layer: classify the task, hand back the arm with measured
    costs where available and priors otherwise; collect ratings to prioritize
    the next controlled batch.
  • The Hermes plugins — the same three, inside your agent's session:
    gonogo for decisions,
    this one's training tool with a human approval hook, and evalroute's
    /route before the first turn.
hermes plugins install keppy/hermes-plugin-thomas
hermes plugins enable thomas

Pairs with hermes-plugin-gonogo:
thomas_encoder_eval saves a gonogo report JSON that gonogo_compare can pair
against a baseline or a later model.

What it adds

| Tool | What it does | Costs |
| --- | --- | --- |
| thomas_check_data | Validates a {text, label} JSONL: schema, label counts, thin labels, duplicates, conflicting labels | free |
| thomas_encoder_train | Fine-tunes an encoder on a Modal L4, temperature-scales it on a held-out split, pulls the artifact back. Runs in the background | GPU time, gated |
| thomas_run_status | running / done / failed, calibration numbers, log tail; lists runs with no argument | free |
| thomas_encoder_eval | CPU inference on held-out cases → gonogo verdict, operating point, calibration error | free |

Plus a pre_tool_call hook, the credit gate, and a bundled skill,
thomas-encoder, that carries the procedure: check first, keep an eval split
the model never saw, poll rather than relaunch, report the interval.

The credit gate

thomas_encoder_train calls through Hermes' pre_tool_call approval hook.
Choose a unique run_name for each attempt. Before asking, the hook validates
the hyperparameters and data, freezes the input bytes, and shows the input
SHA-256 alongside the resolved config and run name. The runner reads only
that snapshot (and checks its hash again before any Modal call), even if the
original file changes during approval. Invalid requests are blocked without
a launch. The rule key includes a fresh approval nonce as well as config and
data hash, so [a]lways does not silently approve repeated launches.
The approval intent is held outside the editable snapshot manifest in the
Hermes process. Editing both files cannot change the approved digest; a Hermes
restart invalidates pending intents, so use a fresh run name. The detached runner
also receives an approved config digest on its command line: editing both its
config and data files cannot authorize different bytes before a Modal call.
A run name is never reused; polling a run does not relaunch it.

This protection applies to plugin tool calls through Hermes, not a
terminal invocation, direct library import or Modal CLI. There is no claim
that the hook can intercept those routes. In a non-interactive session with
no approval bridge, the Hermes tool call fails closed. A denied prompt may
leave a pending snapshot under _approvals/; use a fresh run name for a new
approval or explicitly remove the stale entry. Run data (including the
snapshot) stays local under the runs directory.

Contract

The plugin reads thomas artifacts against thomas's
docs/CONTRACT.md:
the confidence definition, the artifact layout, the split rules. It supports
contract version 1 (thomas 0.2.x). An artifact with a newer
contract_version in its metrics.json is refused with a message to update
the plugin, rather than read wrong.

Setup

The plugin itself only needs gonogo-eval, which Hermes installs from
pyproject.toml. Training and inference run in a separate Python so torch and
Modal stay out of the Hermes venv:

This plugin revision needs thomas-train v0.2.1 or later (the contract it
reads shipped there; v0.2.4 is the first on gonogo-eval 0.4) and
gonogo-eval 0.4; thomas v0.2.0 is not compatible:

git clone --branch v0.2.4 https://github.com/keppy/thomas && cd thomas
uv venv && uv pip install -e ".[encoder]"
.venv/Scripts/modal token new        # or .venv/bin/modal on macOS/Linux

Set the nonsecret runtime setting THOMAS_PYTHON in the environment
of the process that starts Hermes, then restart Hermes to inherit it. For
example, from Git Bash on Windows:

export THOMAS_PYTHON="C:/path/to/thomas/.venv/Scripts/python.exe"
hermes

On macOS/Linux, use the venv's bin/python instead. The plugin itself
registers without this setting; only train and CPU eval need it. Do not
put it in Hermes' .env: requires_env is the install-time credential
prompt, and the interpreter path is neither a secret nor a plugin admission
requirement. The catalog correctly keeps requires_env: [].

Runs live under $HERMES_HOME/thomas/runs/<run_name>/ (config.json,
status.json, log.txt, artifact/). Set THOMAS_RUNS_DIR to put them
somewhere else.

Checked against a real artifact

thomas_encoder_eval was run on the original local-only Banking77
ModernBERT-small-v2 artifact and 250 held-out cases (target 95%). The model
and case text are not tracked in either repository or available at an
advertised download URL. The thomas repo ships a text-free
per-case prediction receipt
and CPU replay script; these reproduce the gonogo decision without the
checkpoint, but do not reproduce inference from an independently downloaded
model:

AUTOMATE WITH REVIEW — overall pass rate 87.2% [82.5%, 90.8%] misses the 95% target,
but abstaining below confidence 0.91 reaches 98.3% precision on 71% of cases
Operating point: threshold 0.912, coverage 71.2%, 72 deferred, precision 98.3% [95.2%, 99.4%]
Calibration error 0.03
Note: The 0.91 threshold was chosen by searching this same case set, so its precision
is optimistically biased. Re-measure it on fresh cases before relying on it.

That's the same verdict as the gonogo example script run on the same artifact.

A previous live run through the plugin, start to finish (not repeated in this review): thomas_check_data →
thomas_encoder_train (approval gate) → Modal L4 → thomas_run_status →
thomas_encoder_eval. The config was deliberately tiny (801 training rows,
about 10 per label, 1 epoch), so the model is weak and the verdict says so:

DO NOT AUTOMATE — pass rate 15.2% [11.3%, 20.2%] is below 50%
(trained in 85 s; T = 0.60; calibration error 0.07; chance is 1.3% on 77 labels)

The first artifact pull hit Modal's stale-volume snapshot and the built-in retry
recovered, which is why the retry is there.
Confidence comes from thomas.encoder_train.scaled_softmax, the function the
training run used to fit the temperature, so there's one definition end to end.

Development

pip install -e ".[dev]"
pytest
hermes plugins validate . && hermes plugins doctor .

The tests need no GPU, no Modal and no thomas install. Training is exercised
with a fake runner subprocess, and the verdict is checked on canned predictions.

License

MIT

README from the reviewed commit 7302ab9; it updates when the author re-pins.

More by keppy

3 plugins by keppy in the catalog.

keppyevalrouteRoute a task to the right (model, reasoning effort) arm before you start. Classifies the task into a lane, prints a provenance-carrying route card, and installs per-model reasoning-effort defaults. Strong rules run locally; weak-signal classification calls the host LLM and consumes tokens. The separate harness and shim can make paid provider calls when explicitly run; no plugin-owned credentials. Disclosure — weak-signal task classification sends the task text to your active Hermes model via the host LLM facade (token cost). The model-callable tool, /route, /rate and observed /model and /reasoning commands append task text and command args to a local ledger at <HERMES_HOME>/evalroute/labels.jsonl. "hermes evalroute install-routes" rewrites agent.reasoning_overrides in config.yaml when you run it. The bundled harness and Hermes shim are never run by the plugin. When run by hand they make paid provider calls, execute model-generated code on the host via shell checkers, and run hermes -z with approvals bypassed. Disclosure — "hermes evalroute sync" (run by hand) downloads the route table from the Hugging Face dataset keppy/evalroute-flywheel into HERMES_HOME and route cards then show that dataset's text; "hermes evalroute dispatch" (run by hand) spawns a single-query hermes chat on the routed model (Nous provider by default) — or, with --runner claude-code, a `claude -p` (Claude Code) process under the user's own Claude account, whose per-run cost is recorded in the local sidecar — with the brief as its query, inheriting the current environment except HERMES_YOLO_MODE (the worker never inherits --yolo; its approvals follow approvals.single_query_mode, default deny), and writes its report and a .dispatch.json sidecar beside the brief. Disclosure — "hermes evalroute contribute" (run by hand only) refuses to run unless evalroute.contribute: true is set in config.yaml, and then uploads redacted outcome rows only — lane, routed arm, confirmed arm, verdict, method, facet counts, ISO week and an HMAC-SHA256 task hash; never task text, notes, paths or the salt — to the user's own path (contributed/<hf-username>/) in the Hugging Face dataset keppy/evalroute-flywheel using the user's existing huggingface_hub login; "--dry-run" prints the exact rows and sends nothing; the per-install salt stays local in <HERMES_HOME>/evalroute/contribute.salt. Since evalroute 0.9.1 (schema 2) the same upload also carries lane-correction rows as lane pairs (from_lane, to_lane, method, week) with no task text, plus the max_turns value and harness name the arm ran under and, since evalroute 0.10.0, whether the arm was an explore draw and which default arm it replaced (arm ids only). Disclosure — since evalroute 0.10.0, setting evalroute.explore.fraction in config.yaml (or EVALROUTE_EXPLORE) makes that fraction of routes recommend the lane's runner-up arm from the bundled explore table instead of the default arm (and "hermes evalroute dispatch" then runs that runner-up model), marked on the route card; off by default. Disclosure — "hermes evalroute install-encoder <dir|owner/name>" (run by hand only) downloads a small text-classification model (the default id keppy/evalroute-lane-encoder is a 146 MB ModernBERT fine-tune on the Hugging Face Hub) into <HERMES_HOME>/evalroute/encoder/ and from then on "hermes evalroute route" runs it locally, offline, to classify the task when the keyword rules are weak, instead of the LLM call; it needs torch and transformers in the plugin environment (pip extra evalroute[encoder]), which the plugin does not install; "install-encoder --remove" deletes it. "hermes evalroute export-cases" (run by hand only) writes your --lane-pinned route rows, task text included, to a local file you name, for training your own encoder; stdout carries counts only and nothing is uploaded. As of 0.6.2 the plugin is a thin adapter: all behaviour lives in the PyPI package evalroute (>=0.10.0,<0.11), declared as a Python dependency that Hermes installs on your confirmation.Communitytools1keppygonogoTurn a pilot-scale eval score into a deployment decision — Wilson intervals, a confidence threshold that routes uncertain cases to a human, and an honest INSUFFICIENT EVIDENCE verdict when the sample cannot support the claim. Disclosure — gonogo_report writes report JSON/HTML to the path the model supplies.Communitytools