Supersedes draft PR #24 (hermes/tts-voice-hfc-female): Brad changed the
decision after that task landed, so this starts fresh from origin/main
instead of building on it.
Bakes three checksum-pinned Piper voices (en_US-amy-medium,
ru_RU-irina-medium, es_MX-claude-high) alongside the existing lessac set,
and adds deterministic, allow-listed language routing to
hermes-jetson-tts-server.py: an explicit request "language" field maps
through a fixed dict to one of the three baked voices, with unknown,
missing, or malformed input always falling back to English amy. A
client-supplied "voice" field is never read, so no client input can reach
a filesystem path. All three voices are eagerly preloaded at process
start (measured ~243MB RSS for three vs. ~88MB for one).
The WebUI has no signal for the language of the text it is about to
speak (verified: hermes-webui-atlas-voice.js sends only text and engine),
so no client- or server-side language detection is added; this gap is
documented in NOTES.md and the PR description rather than papered over.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The composer routing chip showed bare AUTO/FAST/BALANCED/DEEP/MAXIMUM.
Relabel it to Automatic/Fast/Balanced/Deep/Maximum and align the
auto-route model option labels to Automatic · Fast/Balanced/Deep/Maximum
so they match the panels.js profile-default mapping and read as a
recognizable model name. Manual model names (Claude · Opus, etc.) are
unchanged. Update the image-build guard and its quality test to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three narrowly scoped Hermes reliability fixes backed by live evidence
from the Cassandra/titan-iac proof run.
Worker concurrency. Three simultaneous direct CLI workers on the 4-core
hermes-agent node drove load to ~45 and made the hermes and oauth2-proxy
containers fail their probes, leaving the pod 8/10 Ready; two workers
stayed at 10/10. Cap HERMES_CLI_LANE_CONCURRENCY at 2 and lower the
cli-lane-runner CPU limit from 3 to 2 so the dashboard and auth sidecars
keep a guaranteed share of the node. Requests are unchanged: the pod
still asks for 745m total, so placement does not move.
Service links. Kubernetes injects a service-link variable pair for every
service in the namespace, and hermes-claude-broker produces
HERMES_CLAUDE_BROKER_PORT=tcp://10.43.31.76:9006 — a value the broker
parses as an int. That contaminated worker and test environments even
though the deployment already addresses every service by DNS name. Set
enableServiceLinks: false on the hermes-agent pod spec.
Blocked-task scheduling. create_task(initial_status="blocked") records a
created event carrying status=blocked but never a blocked event, while
_has_sticky_block() only inspects blocked/unblocked events. recompute_ready()
considers blocked tasks, so an explicitly parked task with no incomplete
parent auto-promoted on the next dispatcher cycle. Teach _has_sticky_block()
to also recognize a created event whose payload status is blocked, which
covers tasks created before this image patch without adding a persisted
field. Dependency-driven promotion and the circuit-breaker failure-limit
guard are untouched; unblock_task() still releases either kind of block.
hermes-kanban-blocked-regression.py runs against the real upstream
kanban_db API during the image build, so the build fails if any of these
semantics regress.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>