ml0x/Case study, Hermes on GLM 5.3 Flash/Implementation plan

Public copy of a private working report from 2026-09-17. Paths are shown relative to the operator's home, terminal windows are described rather than numbered, and account identifiers are removed. Every number, table and finding is unchanged. Part of the case study Tuning Hermes Agent on GLM 5.3 Flash, one day, measured. Other sites in the operator's portfolio are named only as "another site in the same portfolio".

Hermes implementation plan

Instruction for a future Fable session, derived from the Hermes operations audit of 2026-09-17.
The same text as ~/hermes-reports/HERMES-IMPLEMENTATION-PLAN.md, the instruction for a future Fable session. Workstream detail, helpers and measure.py are under ~/hermes-reports/plan/. Nothing here has been applied.

Hermes tuning

Implementation brief for a Fable session

Written 2026-09-17 08:00 UTC from the audit at ~/hermes-reports/hermes-audit-2026-09-17.html. This file is the instruction. The five workstream files under ~/hermes-reports/plan/ hold the verified line references, code and tests; read the one for the wave you are executing before touching anything.

0. Ground truth you must not re-derive

1. Rules for the implementing agent

  1. Back up before every write: cp <file> <file>.bak-<tag>-$(date +%Y%m%d-%H%M%S). Vendor files under ~/.hermes/hermes-agent get a .diff saved to ~/hermes-reports/plan/patches/ because hermes update stashes local edits.
  2. Read-only on ~/.hermes/state.db (?mode=ro). Never run hermes interactively; the only permitted Hermes invocations are the smoke tests named in wave 4, which cost under $0.01.
  3. No paid API beyond: the two 5-token proxy probes in the proxy acceptance script, the portal account GET, and the catalog GET. State the call budget before any subagent is spawned; forbid paid APIs in its brief.
  4. Measure before and after every wave with python3 ~/hermes-reports/plan/measure.py --since <apply-time-ISO>; the before snapshot is plan/baseline_run2.txt. A wave passes only when its acceptance rows pass; on the stop rule, roll back that wave and record why.
  5. Apply waves in order. Waves 1 and 3 are config-only and are what stretch the credit runway; do not let the proxy or runner builds delay them.
  6. Report in the format of section 6, with numbers from measure.py, never from memory.

2. Wave 1

Context hygiene (config.yaml, 10 min apply, 2 h measure, runs with goals live)

Detail: plan/plan_config.md steps 1 to 8; helper plan/apply_tuning.py (ruamel round-trip, keeps comments) or plan/setkey.py key value.

Key Set to Why (measured)
compression.threshold_tokens 48000 floor put compaction at 152,064 on the hardcoded 202,752 window; fired 5 times all day
compression.proactive_prune_tokens 24000 deterministic prune, no model call
compression.proactive_prune_min_result_chars 4000 51k-char reads inlined
compression.protect_last_n 10 20 protected messages is 50k untouchable
compression.summary_model delete dead key since migration v17
tool_output.max_bytes 20000 default 50,000 chars
terminal.timeout 120 23 exit-124 today; reaches the tool only via TERMINAL_TIMEOUT env bridge at process start
agent.max_turns 40 150/150 exhausted once; goal loop re-enters anyway
updates.pre_update_backup true 14 agent-written skills at stake
approvals.mode 'off' quoted bare off parses as boolean False; works by accident today

Then purge ~/.hermes/memories/MEMORY.md under its flock (procedure in plan_config.md step 8; 1,836 bytes of August notes injected into every prompt). Takes effect per session at its next start; do not restart live goals, let them finish. Acceptance (plan_acceptance.md C1 to C4, T3): prompt p90 under 60k within 2 h of the first new session, compaction 2 to 20 per 1,000 calls, cache-hit share stays above 90 percent (prune can break the prefix cache; that is the stop rule), exit-124 under 3 per hour.

3. Wave 2

Proxy hardening (proxy.py + policy.json, one restart in a 10 min quiet window, 4 h soak)

Detail: plan/plan_proxy.md, in this order, shipped as one restart.

  1. Step 0: env overrides MODELLOCK_ROOT / MODELLOCK_PORT / MODELLOCK_AUTH_JSON so the acceptance script runs a second instance on 8799 against a scratch copy.
  2. Drain the request body on unrecognised POSTs (the 94 "Bad request syntax" 400s a day) and terminate Hermes's provider sweep with a 200 on /version carrying a version key. Do NOT answer /api/tags with a models key: Hermes would classify the proxy as Ollama and start /api/show probes.
  3. Fail-closed load_state(): write a ledger anchor event at startup and on SIGHUP, rebuild from the last anchor on a corrupt state.json, 503 when no anchor exists, never zeros. Run --anchor once at deploy because the 07:30 rebase left no anchor row.
  4. Warning headers X-ModelLock-Remaining-USD and X-ModelLock-Warning at 80 percent of any cap, plus a WARN line to ~/.hermes/modellock/warn.log.
  5. Degrade instead of 402 when only a per-model cap is hit: rewrite to the other allowed model and overwrite the reasoning field with the target's override (glm rejects effort: none with HTTP 400). Global and bucket caps still 402.
  6. /stats JSON endpoint and SIGHUP log reopen (launchd holds the fd; a plain newsyslog rename leaves an empty file).

Acceptance (P1 to P7): zero bad-syntax 400s, sweep 404s per hour under 5 excluding /api/v1/models, state.json survives a deliberate corruption test on the scratch instance, two real 5-token calls cost under $0.0005 each with cached_tokens present. Rollback: restore proxy.py.bak-* and kickstart.

4. Wave 3

Agent behaviour (config plus one hook, one working day to measure 20 tasks)

Detail: plan/plan_delegation.md. Config and hook first, vendor patches last.

  1. delegation.max_iterations: 30, max_concurrent_children: 2, child_timeout_seconds: 900 (hot-read).
  2. skills.creation_nudge_interval: 0 (curator becomes opt-in; 48 skill_manage errors today were 60-char description refusals) and memory.nudge_interval: 30.
  3. platform_toolsets.cli: drop bfl, image_gen, tts, computer_use, session_search (about 6k tokens per call; delegate_task has no toolsets argument, so this is the only lever). Needs per-tab restarts at goal boundaries.
  4. Pre-spawn budget hook: a pre_tool_call shell hook in config.yaml hooks: matched to delegate_task that GETs http://127.0.0.1:8788/healthz (already serves spent_today_usd and caps) and blocks the spawn under a reserve. Script text is in plan_delegation.md step 2.
  5. Delegation contract as a skill named delegation-contract the parent pastes into context: deliverable path written by iteration N-10, budget stated every 10 iterations, no sleep polling. Survives hermes update.
  6. agent.verify_on_stop: false in pipeline profiles only (or HERMES_VERIFY_ON_STOP=0); 18 verification_required finishes and 243 s npm test runs after JSON edits.
  7. Vendor patches, each saved as a .diff: exit reasons billing_blocked / provider_error / max_iterations / incomplete in tools/delegate_tool.py (the child already carries failure_reason: billing, never read); busy-parent drain of completed delegations through the steer rail (delivery lag p50 15 min is the parent's turn length); curator prompt states the 60-char limit.
  8. Operator pipeline fixes in the operator's SEO pipeline repo: orchestrator.mjs must push cached-stage issues into allIssues (REPORT.md said clean over an ok:false human check); L12 sign-off requires a person in by and an operator-minted token, rejects autonomy wording; deployed:true only from a 13-prod-deploy.json written from real wrangler output.

Acceptance (D1 to D4, S1, T1): max_iterations share under 15 percent over 20 tasks, zero 402 deaths labelled max_iterations, delivery lag p50 under 3 min, first-call prompt under 12k tokens, skill_manage errors zero.

5. Wave 4

Budgets, portal guard, runner (live, one pipeline per quiet hour)

Detail: plan/plan_proxy.md steps 5 to 9 and plan/plan_runner.md.

  1. Proxy buckets: the inbound bearer value (NOUS_API_KEY=pl-<name> per profile) becomes the budget key; buckets: section in policy.json; bucket field in the ledger. Unknown bearer falls into default.
  2. Portal guard: every 50 calls or 5 minutes, curl -K - (token off argv) to /api/oauth/account, store credits and member spend in state["portal"], cap on total_usable_credits - spent_since_read <= 1.10. On 401 or expiry fall back to local caps, log drift, never rebase automatically.
  3. Live streaming relay: keep curl, Popen with -N --speed-limit 1 --speed-time <stall> --max-time <per-model>, price partials fail-high as partial_estimate.
  4. Runner at ~/hermes-pipelines/: use hermes -p pl-<name> chat -q "<step>" -Q --yolo --max-turns N --skills ... --usage-file ..., not -z (-z ignores --skills, has a fixed 90-iteration cap and exits 0 on partial runs). Profiles via hermes profile create pl-<name> --clone --no-alias; each profile .env carries its own NOUS_API_KEY=pl-<name> and HERMES_VERIFY_ON_STOP=0. Classify outcomes from usage.json plus the ledger bucket, resume on 402 with --resume <session_id>, back off 30 to 600 s on 502, exit 75 for the launchd tick. Smoke test: the 2-step /tmp write-and-verify, about $0.002.
  5. Migration order: the first site pipeline (4 phases map to steps), then the other site pipelines, then the two non-site pipelines as single-step pipelines. Kanban lanes afterwards: hermes kanban create --assignee pl-<name> --max-runtime --max-retries, dispatcher in the gateway.

Acceptance (P4 to P9, R1 to R3): zero 402 while usable credits exceed the reserve, per-bucket cost visible in /stats, smoke pipeline completes twice with cost under $0.006, no session ever needs a keystroke to resume.

6. Report format when done (per wave)

Projection to hold yourself to (plan_acceptance.md section 5): $2.11 a day today; $1.33 to $1.64 after waves 1 and 2; about $1.12 after waves 3 and 4. Runway on the spendable balance: 83 h today, about 119 h after waves 1 and 2, 157 h after all four.

Implementation results, 08.45 UTC

All four waves shipped

88
Implementation quality, self-rated against the plan (95 needs the measured after-window, see below)
17
config keys applied and verified through the Hermes loader
58 / 0
proxy acceptance checks pass / fail, zero-spend suite
160 / 1
vendor tests pass / fail after patches (the 1 is pre-existing, order-dependent)
197 / 197
pipeline tests pass, plus 32 new gate checks
$0.0012
smoke pipeline, 2 steps, 47 s, isolated profile

What the operator does now

Every Hermes session from before 08:20 UTC has finished its goal and sits idle; all five carry the old config in memory. Start new sessions (hermes, or hermes -p pl-site-a for the first pipeline) and let them work for two hours. Then run:
bash ~/hermes-reports/plan/after.sh
It prints the before-and-after table with PASS or FAIL per criterion and a score. The apply time it compares against is 2026-09-17T08:20:44Z. Until then this section is the delivery record, not the outcome.

Shipped, by wave

WaveShippedEvidenceRollback
1 Context hygienecompression threshold 48k, prune 24k at 4k chars, protect_last_n 10, dead summary_model removed, tool output 20k, terminal timeout 120, max_turns 40, personas removed, pre-update backup on, approvals mode quoted; MEMORY.md purged (5 stale entries)Loader readback of every key; purge under the memory lockconfig.yaml.bak-tune-20260917-152059; memories/MEMORY.md.bak.*
2 Proxyproxy.py 571 to 1,485 lines: body drain and sweep terminator, fail-closed state with ledger anchors, 80 percent warning headers and warn.log, degrade to the other model on per-model caps with reasoning rewrite, bearer-keyed buckets (pl-smoke 0.50, pl-site-a 3.00), portal-truth guard with 1.10 reserve, live streaming with stall cut and fail-high partial pricing, --refresh-rates, /stats, SIGHUP log reopen, newsyslog conf58 zero-spend checks pass; 3 real 5-token probes; restart gap 0.27 s; first portal read: drift 0.011 vs ledgerproxy.py / policy.json / state.json .bak-impl-20260917-083237 plus kickstart
3 Behaviourdelegation 30 / 2 / 900 s, curator off, memory nudge 30, toolsets trimmed to 10, budget-gate hook on the proxy health endpoint, delegation-contract skill plus SOUL.md rule; vendor patches 01-05: true exit reasons, child deliverable contract, busy-parent result drain through the steer rail, curator 60-char rule, verify-on-stop scoped to code filesHook blocks at under $1 headroom in a dry test; delegate tests 70 pass; drain tests 9 pass; patches re-apply byte-identical onto a clean exportplan/patches/*.diff and REAPPLY.md; git checkout of the vendor tree
3 Pipeline gatesCached-stage issues counted, any ok:false stage forces FAIL, evidence counts from real stage outputs; L12 sign-off needs a person, an operator token in a file the pipeline only reads, and no autonomy wording; deployed:true only from a recorded wrangler output hash plus served-vs-dist compare; PHASE-4 docs rewritten to stop at the gate197 of 197 existing tests plus 18 gate and 14 status checks; today's real approval.json is now rejected*.bak-gate-* beside each file
4 RunnerProfiles pl-smoke and pl-site-a (own state.db, config, bearer key, verify-on-stop off); ~/hermes-pipelines with runner.sh, cost.py, classify.py, prompts, launchd template (not loaded), README and MIGRATIONSmoke: 2 steps, 47 s, $0.001211 for 5 calls, root config sha unchangedDelete the two profile dirs and ~/hermes-pipelines
Measuremeasure.py with sessions-started-after and bucket split, compare.py with 12 weighted criteria, after.sh one-command readout, before snapshots savedDry run on an empty after window exits 0 and says so; synthetic split proves the scoring path-

Skipped or deferred, stated plainly

Why 88 and not 95

Everything in the plan that could be built without a live after-window is built, tested and reversible, and the two integrity bugs are closed. The seven points withheld are the ones only measurement can award: the compaction threshold and prune have never run under real load (the cache-share stop rule in wave 1 is a real risk), the busy-parent drain has unit tests but no production delivery yet, and the first site pipeline's steps are unverified against their source documents. Run after.sh once two hours of new sessions exist; if C1 to C4 and D1 to D3 pass, the setup is at the 95 mark.

Sources: plan/impl/impl_proxy.md, impl_vendor.md, impl_pipeline.md, impl_runner.md, impl_measure.md; backups named per row. Live proxy stats at http://127.0.0.1:8788/stats.