atlas-iac/docs/hux/HANDOFF.md
2026-09-01 20:43:50 -03:00

569 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# hermes-next: Claude backend handoff ledger
Branch `feature/hermes-next-hux` (origin, pushed after every card); draft PR #55 https://scm.bstein.dev/titan/atlas-iac/pulls/55 (opened through the SCM broker, never merged by Claude). Board
`hermes-next` on the operator Hermes Kanban; cards HUX-01..HUX-12 are parked
(`blocked`) on purpose so the CLI lane runner never auto-claims them — the
Claude coordinator and the Codex root session are the only implementers.
Contract: `hux.v1` 1.0.0 (`services/hermes/contracts/hux/VERSION`,
`docs/hux/ADR-0001-hux-v1-contract-freeze.md`). Threat model:
`docs/hux/THREAT-MODEL.md` (SO-01..SO-54). Data model: `docs/hux/DATA-MODEL.md`.
Run everything with the CI interpreter:
`python -m pytest testing/tests/test_hermes_hux_*.py -q` and
`python -m coverage run --branch --source=dockerfiles/hermes-hux-foundation -m pytest testing/tests/test_hermes_hux_*.py`.
## Codex integration requirements common to every card
(Superseded 2026-08-24 where it conflicts with "Integrated topology" at the
end of this file: the manifests, plugin, router headers and Flux wiring are
now in-repo, the data root moved off the tenant PVC, and the Worker has no
HUX yet.)
- Package `dockerfiles/hermes-hux-foundation/hux/` runs as `python -m hux.server`
inside each `hermes-chat-tenant` pod (and the Worker) on `127.0.0.1:8790`.
Image, manifest, NetworkPolicy and Flux wiring are Codex's (SO-01, SO-02,
SO-45: separate uid, `/opt/data/hux` mode 0700).
- Env: `HUX_DATA_ROOT` (tenant PVC, default `/opt/data`), `HUX_TENANT_SLOT`
(pin, SO-03), `HUX_FLAGS` (comma list, default empty = everything off),
`HUX_RELAY_KEY` (Telegram relay), `HUX_WORKER_KEY` (Worker),
`HUX_BUILD_COMMIT`, `HUX_IMAGE_DIGEST`, `HUX_CONTRACT_DIR` (schemas path in
the image; default resolves relative to the repo layout).
- Router → service headers (SO-04): `X-Hermes-Tenant-Identity: slot-N`,
`X-Hux-Subject: usr_<hash>`, `X-Hux-Surface: chat|telegram|voice|api|worker`,
optional `X-Hux-Trust: relay|worker` + `X-Hux-Relay-Key`. The router must
strip any inbound `X-Hux-*` before setting its own.
- First client call: `GET /hux/v1/capabilities` → which cards/routes are on.
Disabled or unknown → `404` (`flag_off` vs `not_found` only in the body).
- Mutations: `If-Match: <revision>` on revisioned records (409 on mismatch);
`Idempotency-Key` on creates. Errors are `hux.error.v1`.
- Fixtures Codex codes against: `services/hermes/contracts/hux/examples/*.json`
(validated in CI by `test_hermes_hux_contract_schemas.py`).
## HUX-11 shared foundation — DONE (backend)
| | |
|---|---|
| Commits | `b4145861` (contract freeze 1.0.0), `6a5e0d87` (service core) |
| Files | `services/hermes/contracts/hux/{common,identity}.schema.json` + examples, `VERSION`; `dockerfiles/hermes-hux-foundation/hux/{__init__,errors,identity,flags,store,audit,http,foundation,server,contracts,rules}.py`; `docs/hux/ADR-0001-*.md`, `THREAT-MODEL.md`, `DATA-MODEL.md` |
| Tests | `testing/tests/test_hermes_hux_contract_schemas.py` (61), `test_hermes_hux_contract_foundation.py` (30): identity header rejection matrix, relay/worker keys, slot pinning, fail-closed flag chains, capabilities record validity, tenant-scoped paths + traversal rejection, revisions/conflicts, torn-ledger recovery, blobs, manifest, concurrent writers, audit rows, flag-off == not-found, 401 never touches storage, real HTTP server JSON + SSE, loopback-only bind, all errors validate, ≤500 LOC guard |
| Coverage | 99% line / 98% branch over the package |
| Contract | 1.0.0 |
| Flag | `hux.foundation` (root of every dependency chain) |
| Risks | Rate limiting (SO-53) and hash-chained audit (SO-46) not yet implemented — tracked for the Wave A review; relay callers still carry `X-Hux-Subject` (deviates from SO-06; the slot→owner mapping lives in the router, so the header is redundant but harmless and lets the service pin the subject) |
| Codex needs | Wire the service into the tenant pod + Worker, NetworkPolicy, router header contract above, `HUX_FLAGS=hux.foundation` for the first canary; `testing/quality_contract.json` may add `dockerfiles/hermes-hux-foundation/**/*.py` to `line_limit_globs` and the two moved modules to `managed_modules` (Claude does not edit that file) |
## Wave A backends — DONE, awaiting adversarial review and Codex integration
Full suite: `testing/tests/test_hermes_hux_*.py` = 325 tests, 99% line / 99% branch over `dockerfiles/hermes-hux-foundation/hux/` (every family module 99100%). Every module ≤ 500 LOC (guarded by tests). Contract revised additively to 1.1.0 (ADR-0001).
| Card | Commit | Files | Tests | Flag | Codex needs |
|---|---|---|---|---|---|
| HUX-01 activity timeline | `b8fb72fb` | `hux/events.py`, `hux/redaction.py` | `test_hermes_hux_contract_events.py` (34): ordering, idempotency, replay/reconnect, redaction by surface, cancellation receipts, cross-tenant | `hux.activity_timeline` | Router forwards `Last-Event-ID`; telegram/voice surfaces get partial redaction; agent hook posts events with `Idempotency-Key`; `HUX_CANARY_FILE=/opt/data/.env` so secrets are scrubbed |
| HUX-02 memory center | `b8fb72fb` | `hux/memory.py` | `test_hermes_hux_memory_ledger.py` (20), `test_hermes_hux_memory_retrieval.py` (7): no-store, suggest-only, edit/supersede, forget, retrieval removal, export, If-Match | `hux.memory_control` (needs `hux.privacy`) | Agent memory tool must call `hux.memory.retrieve` semantics (tombstones first) instead of the upstream memory file; UI per-message controls call `/memory/{id}/{action}` with `If-Match` |
| HUX-10 privacy | `b8fb72fb` | `hux/privacy.py` | `test_hermes_hux_privacy_topics.py` (19), `test_hermes_hux_privacy_retention.py` (6) | `hux.privacy` | Schedule `run_retention` daily (no thread inside the service by design); surface `HUX-Audit-Stale`; show notices and post `chosen` |
| HUX-04 artifacts | `9f0ffe03` (+ upload caps, project check in the 1.1.0 batch) | `hux/artifacts.py`, `hux/diffs.py` | `test_hermes_hux_artifact_versions.py`, `test_hermes_hux_artifact_auth.py` (immutable versions, concurrency, lineage forgery 404, caps 413, diffs, promotion) | `hux.artifacts` (needs `hux.projects`) | Version content served with `nosniff` + attachment; uploads ≤ 25 MiB content via base64; sharing intentionally not implemented (`access.mode` always `owner`) |
| HUX-08 research | `9f0ffe03` | `hux/research.py` | `test_hermes_hux_research_citations.py`, `test_hermes_hux_research_notebook.py` (dedupe, integrity, notebook state machine) | `hux.research` | Service never fetches URIs (SO-19): the web tool records sources/passages after its own fetch; citation strip = `GET /messages/{id}/citations` (passages + sources embedded) |
| HUX-05 autonomy | `3ebee2cc` | `hux/policy.py`, `hux/budgets.py` | `test_hermes_hux_policy_matrix.py` (22), `_approvals.py` (22), `_receipts.py` (13) | `hux.autonomy` (needs `hux.activity_timeline`) | Agent hook: `POST /approvals` before any external side effect, `POST /runs/{id}/gate` immediately before executing with the canonical argument hash, `POST /runs/{id}/budget` per turn, `POST /runs/{id}/stop` with `process_registry_empty` from the real process registry; decisions only from human surfaces (worker trust → 403) |
| HUX-03 organization API | `3ebee2cc` | `hux/organization.py` | `test_hermes_hux_contract_organization.py` (14) | `hux.projects` | Codex owns the UI + migration of upstream WebUI projects; search over `message_text` is a later increment |
Known gaps carried to the Wave A review: rate limiting (SO-53), hash-chained audit (SO-46), retention scheduler ownership (Codex cron vs service thread), approval expiry applied lazily on read, linear scans for idempotency/dedupe (fine at documented caps).
## Wave A adversarial review — CLOSED
A fresh reviewer attacked HEAD `1b1a14e9` (tenant isolation, privacy, autonomy, rollback) and reported 13 findings; the repro harness lives outside the repo. Every finding is closed by a regression test that cites it:
| # | Severity | Finding | Fix commit |
|---|---|---|---|
| F1 | critical | worker/api trust could `PUT /policy` and self-approve | `6964a9d8` — policy writes and allow grants are human-surface only |
| F2 | high | worker trust could read every tenant record (SO-08) | `6964a9d8``flags.WORKER_ROUTES` allowlist enforced in `Router.dispatch`; unexpected exceptions become audited 500 records |
| F3 | high | memory `edit` skipped topic/sensitivity/private-mode gates | `681b0408` — one `_classify` path for proposals and edits |
| F4 | high | a `session` approval for an external effect released unrelated later effects | `6964a9d8` — external effects match only the same run + argument hash; gate conversation from the run, not the body |
| F5 | high | seq duplicated after a crash between append and checkpoint | `681b0408` — seq = max(checkpoint, ledger tail + 1) under the lock |
| F6 | medium | gate ignored budget exhaustion | `6964a9d8` |
| F7 | medium | stale unconditional write could resurrect a forgotten memory | `681b0408` — re-read under lock, always write with the loaded revision |
| F8 | medium | any caller could assert `process_registry_empty`; failed receipts were sticky | `6964a9d8` — gateway (worker trust) only; failed receipts supersedable |
| F9 | medium | secrets stored verbatim in titles/passages/claims/notebooks; forget left the title | `681b0408` — scrub applied; forget blanks the document. Artifact bodies stay verbatim (user-owned) but are forced `restricted` and audited (`a75ca299`) |
| F10 | medium | SO-46/48/53 claimed but absent | threat model amended (`74c65499`): tracked as open, not claimed |
| F11 | low | stored receipts carry `revision` the schema forbade | `6964a9d8` — optional `revision` on the receipt |
| F12 | low | capabilities advertised routes without handlers | `6964a9d8` — HUX-06/09/12 routes empty until shipped |
| F13 | low | misc (healthz version, ghost conversations from notices, idempotency outside the lock, artifact titles not searchable, unnormalised paths, multi-hash once approvals) | `6964a9d8` + `681b0408` |
Suite after repairs: 377 tests, 99% line / 99% branch over `hermes-hux-foundation` and `hermes-worker-hux`. Full repo gate: same 13 pre-existing, unrelated failures as `main`.
## Worker hook library — DONE
| | |
|---|---|
| Commit | `aeef0f84` |
| Files | `dockerfiles/hermes-worker-hux/hux_hook/{__init__,client,hooks}.py`, `NOTES.md` (wiring guide for the runtime patch, which is Codex's file) |
| Tests | `test_hermes_hux_policy_hook.py` (11), `test_hermes_hux_contract_hook.py` (10) — real service in-process, end-to-end approval → human decision → gate released once, canary never persisted, unreachable service fails closed for side effects |
| Codex needs | Agent container env `HUX_BASE_URL`, `HUX_TENANT_SLOT`, `HUX_SUBJECT`, `HUX_WORKER_KEY`; call order per `NOTES.md`: `before_tool` → execute only on `proceed``after_tool`; `record_spend` per turn; `on_stop` returning `None` means the stop is not done |
## Conversation privacy state — DONE
`GET /hux/v1/conversations/{id}/privacy` (HUX-10, worker-callable) returns forgotten / memory_disabled / topics / mode / `memory_writes_allowed`; `hux_hook.memory_gate` now reads it and fails closed. Tests: `test_hermes_hux_privacy_topics.py::test_conversation_privacy_state_route`, hook memory-gate test extended.
## Open items (not blockers for Codex integration)
- SO-46 hash-chained audit, SO-48 single-writer lock. (SO-53 rate limits
shipped since: `hux/http.py` `RateLimiter`, per-subject read/write
buckets, `429` + `Retry-After`, tuned by `HUX_READS_PER_MINUTE` /
`HUX_WRITES_PER_MINUTE` — see the integrated-topology section.)
- Retention scheduler: `privacy.run_retention` is on-demand; Codex decides cron vs sidecar.
- Search over `message_text`; artifact sharing (`shared_readonly`).
- `testing/quality_contract.json` line-limit globs / managed modules for the two new package paths (Codex's file).
## Integrated topology (2026-08-24, local chain, staged — not deployed)
Everything in this section exists only on local `main`. `origin/main` is
still `5558c24f`; Flux reconciles origin, so none of this is running
anywhere. Chat is staged; the Worker is untouched. The chain since origin
(oldest first): `964103f0` merge of the hardened foundation, `438180a9`
tool runtime gated through HUX, `a1070449` vendored runtime hooks,
`53d7c2c5` backend families complete, `31c5eae5` chat router image
automation, `1eda927b` trusted HUX workspace in the WebUI, `47165274`
HUX-12 evidence trust isolation, `b66c762f` HUX card UI models,
`bd63b568` router strips inbound `X-Hux-*`, `9e99470f` one-or-two WebUI
consumers in release rendering, `0b56d04e` quality-contract gates,
`71c05cf9` staged sidecar activation topology, `659f70ee` WebUI
integration contract notes, `13359769` all-or-nothing topology gates.
### Pod topology (`services/hermes/chat-statefulset.yaml`)
Each `hermes-chat-tenant` pod runs a fourth container, `hux`, on the SAME
reviewed WebUI image as the `webui` container
(`registry.bstein.dev/bstein/hermes-webui`, Flux
`$imagepolicy hermes:hermes-webui-release`), started as
`python -m hux.server` with `PYTHONPATH=/opt/hermes-hux`. It binds
`127.0.0.1:8790` (`HUX_BIND`, `HUX_PORT`), runs as uid/gid 10000 with
`readOnlyRootFilesystem`, all capabilities dropped, tmpfs `/tmp`, and
`/healthz` probes. `HUX_TENANT_SLOT=slot-N` comes from the pod ordinal,
as for the agent. Port 8790 appears in no Service or NetworkPolicy;
loopback is the only path (delivery-gated).
### Storage: shared RWX claim, kubelet-scoped subtrees
The plan's `/opt/data/hux` on the tenant `home` PVC is gone. Storage is a
standalone `ReadWriteMany` PVC `hermes-chat-hux-data` (storageClass
`astreae`, 10Gi, `services/hermes/chat-pvcs.yaml`) shared by all tenant
pods, with per-pod isolation enforced by kubelet
`subPathExpr: $(POD_NAME)` mounts:
- `hux` container: `/var/lib/hux` = its own `$(POD_NAME)` subtree;
`HUX_DATA_ROOT=/var/lib/hux/store`.
- `hermes` (agent) container: read-only `/run/hermes-hux-context` =
`$(POD_NAME)/context` and `/run/hermes-hux-subject` =
`$(POD_NAME)/binding` only. It has NO mount of `/var/lib/hux/store`;
records are reachable from the agent only through the loopback API.
- `webui` container: read-only `/run/hermes-hux-context` only.
No container in any pod mounts another pod's subtree.
### Identity provisioning (`init-hux-runtime`)
A dedicated init container (root, capabilities dropped to
CHOWN/DAC_OVERRIDE/FOWNER) provisions, idempotently per pod:
- 0700 roots `binding/`, `context/`, `store/`, owned 10000:10000;
- a persistent 32-byte context key `context/context-key` (0600), created
once and kept across restarts;
- an immutable subject binding `binding/subject` (0440, created `O_EXCL`):
`usr_` + HMAC-SHA256(context-key, `"hux.subject.id.v1\0slot-N"`). On
restart the file is verified (regular file, uid 10000, mode 0440, one
hardlink, expected content) or the pod refuses to start;
- a redaction canary `context/redaction-canary` (0400);
- relay and worker keys in `Memory`-medium emptyDirs
(`/run/hermes-webui-hux/relay-key`, `/run/hermes-hux-worker/worker-key`,
0400) — regenerated on every pod restart, never touching the PVC.
The subject is stable across restarts, derived from a per-pod random key,
and never derived from the Keycloak subject.
### Env contracts
`hux` sidecar: `HUX_FLAGS` lists all twelve cards (`hux.foundation,
hux.activity_timeline, hux.memory_control, hux.projects, hux.artifacts,
hux.autonomy, hux.friendly_modes, hux.multimodal, hux.research,
hux.onboarding, hux.privacy, hux.release_followthrough`);
`HUX_RELAY_KEY_FILE`, `HUX_WORKER_KEY_FILE`, `HUX_SUBJECT_BINDING_FILE`,
`HUX_CONTEXT_KEY_FILE`, `HUX_CANARY_FILE`; rate limits
`HUX_READS_PER_MINUTE=600`, `HUX_WRITES_PER_MINUTE=120`; and build
provenance `HUX_IMAGE_TAG` / `HUX_IMAGE_DIGEST` bound by Flux setters
(`$imagepolicy hermes:hermes-webui-release:tag` / `:digest` comments).
Agent container: `HUX_BASE_URL=http://127.0.0.1:8790`,
`HUX_RUNTIME_ENABLED=1`, and `HUX_TOOL_ENFORCEMENT=0` — the first rollout
is observe-only until approval parking/resume is connected to the
upstream tool loop and proven live — plus `HUX_WORKER_KEY_FILE`,
`HUX_SUBJECT_FILE`, `HUX_CONTEXT_KEY_FILE`, `HUX_TIMEOUT_SECONDS=3`.
### Agent runtime plugin
`services/hermes/plugins/hux-runtime/` renders into the
`hermes-hux-runtime-plugin` ConfigMap (`services/hermes/kustomization.yaml`)
and mounts read-only into the agent at `/opt/data/plugins/hux-runtime`:
`runtime.py`, `tool_policy.py`, `context_ids.py`, `plugin.yaml`
(`on_session_end` hook + `tool_execution` middleware, fail-closed), and a
vendored `hux_hook/` package kept in parity with the reviewed worker hook
library (parity is delivery-gated).
### Router boundary
`services/hermes/router/main.go` deletes EVERY inbound header whose name
starts with `x-hux-` (case-insensitive) at the authenticated tenant
boundary — in the same Director that strips OAuth headers — before
asserting `X-Hermes-Tenant-Identity: slot-N`. Regression tests:
`services/hermes/router/main_test.go`. Defense in depth: the sidecar is
loopback-only anyway, and the in-pod WebUI BFF asserts its own `X-Hux-*`
from trusted context.
### WebUI image (`dockerfiles/Dockerfile.hermes-webui`)
The image ships the service and contracts at `/opt/hermes-hux` (`hux/`
package + `contracts/`, `HUX_CONTRACT_DIR`), the BFF proxy
(`api/hux_bff.py`) and trusted context attachment (`api/hux_context.py`,
`hux.webui_context.v1`), the bootstrap bridge
(`static/hux/bootstrap.js/.css`) and the wave A/B/C runtime bundles. A
build-time gate compiles the package and constructs the router with
`HUX_FLAGS=""`: images carry the code but no activation — flags live only
in manifests.
### Release rendering (`ci/scripts/hermes_webui_flux_release.py`)
The release script updates one-or-two WebUI image consumers in
`StatefulSet/hermes-chat-tenant` (the `webui` container, plus the `hux`
sidecar when staged) and exactly one in `Deployment/hermes`, and binds
`HUX_IMAGE_TAG`/`HUX_IMAGE_DIGEST` to the released tag and digest whenever
the sidecar fields are present — failing when the binding fields are
incomplete (exactly one of each, or none at all).
### HUX-12 evidence trust (`hux/release_security.py`, `hux/releases.py`, `47165274`)
Release follow-through has a dedicated fourth trust class, `evidence`:
- File-only authentication: `HUX_RELEASE_EVIDENCE_KEY_FILE` must be a 0400
regular file (symlinks refused) holding a key ≥ 32 chars, and
`HUX_RELEASE_EVIDENCE_POLICY_FILE` a strictly validated
`hux.release_evidence_policy.v1` allowlist (≤ 16 workloads,
credential-free HTTPS URLs, exact field sets, bounded evidence age).
Without both healthy the HUX-12 capability is off entirely — fail closed.
- Worker trust is read-only on releases (list/read in `WORKER_ROUTES`); it
can no longer create releases or transitions.
- Only router trust creates the single `reviewed` proposal per scoped
workload commit; only evidence trust performs transitions, and its route
allowlist in `hux/http.py` blocks everything else.
- Transition evidence binds exactly: `merged` the 40-hex merge commit;
`built` the CI URL plus image ref / image digest / Harbor digest all
equal; `deployed` a `main@sha1:<40-hex>` Flux revision; `converged`
pod digest == image digest; `live_verified` one passing health receipt;
`rolled_back` an exact target digest.
- Ledger entries are hash-chained (`previous_hash`/`entry_hash`) and
verified on every read; `Idempotency-Key` replays return the recorded
view with `HUX-Replayed: true`.
### Worker state
`worker.bstein.dev` (the `hermes` Deployment and the execution-worker
manifests) has NO HUX sidecar, plugin or environment. Cross-surface
continuity is not claimed; HUX is chat-staged only.
### Test state
Verified 2026-08-24 with the CI interpreter:
`python -m pytest testing/tests/test_hermes_hux_*.py` = 646 passed
(20.8 s). Delivery gates in `testing/tests/test_hermes_hux_delivery.py`
assert the staged topology against the manifests themselves:
all-or-nothing activation, loopback-only sidecar on the reviewed WebUI
image, least-privilege storage and key mounts, init-container identity
persistence + transport-key rotation, file-backed authentication inputs,
and plugin/vendored-hook render parity. UI card model suites:
`testing/tests/test_hermes_hux_ui_*.py`, each driving a Node harness
(`test_hermes_hux_ui_*.mjs`). Release rendering:
`test_hermes_webui_release.py` (18) and `test_hermes_image_automation.py`
(2).
### Superseded statements
- "`/opt/data/hux` on the tenant PVC; SO-45 separate uid, mode 0700" —
superseded. Storage is the shared RWX claim with kubelet `subPathExpr`
isolation above; every container runs uid 10000 and the boundary is
mount scoping, not a uid split. See `THREAT-MODEL.md`, "Integration
amendments (2026-08-24)".
- "`HUX_DATA_ROOT` (tenant PVC, default `/opt/data`)" — now
`/var/lib/hux/store` on the dedicated claim.
- "runs … inside each `hermes-chat-tenant` pod (and the Worker)" — the
Worker has no HUX yet.
- "Image, manifest, NetworkPolicy and Flux wiring are Codex's" — landed
in-repo with the chain above.
- Open item "SO-53 rate limits" — implemented in `hux/http.py`; SO-46 and
SO-48 remain open (see the threat model amendment).
## Completion increments (2026-08-24, later — local chain, staged, not deployed)
Everything below is committed locally on top of the integrated topology and
verified by the full gate (826 passed / 0 failed across
`test_hermes_hux_*.py`, `test_hermes_webui_hux_*.py`, image-automation,
webui-release, auto-router and chat-quality suites, after one fieldRef
tolerance fix in the voice gate).
- **Topology gates are all-or-nothing adaptive** (`test_hermes_hux_delivery.py`,
`test_hermes_image_automation.py`): with no `hux` sidecar the manifests must
contain zero partial HUX wiring; with it, the full strict boundary applies.
This lets the source chain merge and build before activation.
- **HUX-06 adoption** (`services/hermes/plugins/auto-router/hux_mode.py`):
the auto-router consults the loopback HUX service (worker trust, the same
`hux.context.id.v1` HMAC derivation) for the conversation's selected
friendly mode: fast→auto/fast, thoughtful/research→auto/deep,
create→auto/balanced, private→the local route only. Explicit UI picks win;
every failure falls through to previous behaviour. Delivered through the
auto-router ConfigMap with a degrade-to-noop import shim.
- **Private mode is enforced upstream** (`hux/policy.py`, `hux/budgets.py`):
network, web_search, send_message, shell and delegate are refused by the
approval resolver and re-refused by the gate for a conversation whose
stored mode is `private`, regardless of autonomy level.
- **HUX-04/08 emitters** (`plugins/hux-runtime/emitters.py`): after a
successful `write_files` tool the plugin registers the written file as an
artifact (create or immutable version, ≤1 MiB, deterministic idempotency);
after successful `network`/`web_search` tools it records up to three
deduplicated `web` sources from the real output. Fail-open by construction.
- **HUX-03 message-text search** (`hux/organization.py`):
`GET /hux/v1/search?include=message_text` scans stored message events of
the 100 most recently active candidates; forgotten, private, restricted
and fully-redacted content never matches; deterministic cursor pagination.
The default search response is unchanged (the shipped UI requires
`message_text` in `not_indexed`).
- **HUX-12 producer** (`dockerfiles/hermes-hux-foundation/hux_producer/`,
100% line+branch, 71 tests): pure verifier binding review URL prefix →
Jenkins SUCCESS/revision → immutable Harbor tag/digest → Flux
`main@sha1:` revision with pin containment → desired workload image →
every Ready pod imageID → bounded-age health receipt → rollback target;
HTTPS-only injectable collectors (SA token for the API); evidence-trust
driver posts exactly one If-Match transition. Cluster layer staged in
`chat-statefulset.yaml` (sidecar + tolerant key init),
`hux-evidence-policy.json`, `hux-evidence-rbac.yaml` (read-only pods/
statefulset + the single named Kustomization), tenant egress to the API
ClusterIP and traefik. The key reaches only the `hux` and
`hux-evidence-producer` containers.
### HUX-12 enablement runbook (fail-closed until every step is done)
1. `vault kv put kv/atlas/hermes/hux-evidence evidence_key="$(openssl rand -hex 32)"`
2. Add to the chat tenant pod annotations (Flux commit):
`vault.hashicorp.com/agent-inject-secret-hux-evidence-key: kv/data/atlas/hermes/hux-evidence`
plus the matching template emitting `.Data.data.evidence_key`.
3. Create ConfigMap `hermes-hux-evidence-scope` with
`HUX_PRODUCER_PROJECT_ID` / `HUX_PRODUCER_CONVERSATION_ID` for the release
conversation (visible in the UI once a reviewed proposal exists).
4. Only then does `release_security.configured` turn HUX-12 on; the sidecar
loop starts posting verified transitions.
### Staged release sequence (agreed)
1. Push the source tip to main: the full local chain plus a hold commit that
restores `services/hermes/{chat-statefulset,chat-pvcs,chat-configmap,kustomization,networkpolicy}.yaml`
to the live state (keeping only the auto-router `hux_mode.py` ConfigMap
key) and removes the evidence policy/RBAC files — so Flux applies nothing
and CI builds the reviewed WebUI image.
2. Trigger the WebUI build at that exact SHA; wait for Flux digest
convergence.
3. Re-apply the activation topology pinned to the new tag/digest with
`HUX_FLAGS=hux.foundation`, `HUX_TOOL_ENFORCEMENT=0`, and a RollingUpdate
partition canarying ordinal 3 first.
4. HUX-11 live gates → card-by-card flag enablement with lifecycle
acceptance → enforcement after park/resume + stop receipts pass live →
HUX-12 after the runbook above → Worker separately.
## Live canary evidence (2026-08-24 ~09:05 UTC)
Foundation canary LIVE and verified on hermes-chat-tenant-3:
- Commits: source 2f535d3a (build 21), Flux digest 1d29c25c, activation 39a54482 (partition 3, HUX_FLAGS=hux.foundation, enforcement 0).
- Image: registry.bstein.dev/bstein/hermes-webui:git-2f535d3a...-build-21-release@sha256:e5b9b2fa8296a7b8a6065c63e7b288cd3305d70a3acc4312055b819bb5f64c04 on webui + hux + hux-evidence-producer (pod imageIDs verified); agent digest unchanged (sha256:c64c06e6...).
- canary_verify: 15/15 PASS (all containers ready, ordinals 0-2 untouched and huxless, loopback /healthz ok, subject binding present, public https://chat.bstein.dev/healthz 200, Flux Ready).
- Live route matrix: unauth 401, bad key 401, wrong slot 401, relay capabilities 200 with only HUX-11 enabled, worker capabilities 200, disabled card 404 flag_off. Subject usr_224348fa... consistent with the HMAC binding.
- Identity hashes for the restart-persistence gate: context-key 8a1c474d..., subject c4ebe353... (compare after the next roll).
- Rollback target: WebUI build-18 digest sha256:df91f8a3... (previous fleet image); activation rollback = revert 39a54482.
- Retention scheduler reports disabled until hux.privacy is enabled (by design).
Next: Wave-A canary flags (activity_timeline, projects, privacy, memory_control) on ordinal 3, lifecycle gates per card, then widen partition.
## Wave-A live gates (2026-08-24 ~09:12 UTC) — PASS
Canary rolled to ee330822 flags; restart persistence PROVEN (context-key and
subject hashes identical across the roll). Live lifecycle 13/13 on the
canary loopback: capabilities advertise exactly HUX-01/02/03/10/11; real
project + conversation created; worker-emitted event readable by relay;
memory proposed -> approved -> forgotten with the tombstone effective in the
listing; privacy policy served; conversation forget tombstones the record;
retention scheduler enabled and healthy. Next: Wave-B flags (artifacts,
research, friendly_modes, multimodal, onboarding) on the canary.
## Wave-B live gates (2026-08-24 ~09:20 UTC) — PASS 18/18
Canary at dc2b3643 flags. Live on the canary loopback: capabilities show the
Wave-B set with HUX-07 correctly fail-closed (depends_on HUX-05); artifact
create -> immutable version -> content -> diff -> promotion; research source
-> passage -> citation attached to an authoritative message id with the
citation strip readable; provider-neutral mode catalog, thoughtful selected
under If-Match and read back by worker trust; suggestion shown for the real
after_artifact context, explicit clicked dismissal recorded, re-show
suppressed, suppression state durable. Next: hux.autonomy (+ multimodal
unlock) on the canary, enforcement still 0.
## Autonomy-wave live gates (2026-08-24 ~09:30 UTC) — PASS 15/15
Canary at f2f54033 flags. Live on the canary loopback: HUX-05 park ->
human-only approval (worker 403) -> gate releases exactly once -> consumed on
the second ask; denial stays denied; browser cannot write stop receipts;
gateway receipts are honest (process_registry_empty=false ->
failed_to_cancel, superseded by a true empty-registry cancellation). HUX-07
unlocked by the dependency chain and media registration required a REAL
approved artifact_write action before accepting upload metadata.
ENFORCEMENT: park/resume and gateway-owned receipts passed live, so
HUX_TOOL_ENFORCEMENT=1 ships to the canary (partition still 3; ordinals 0-2
remain untouched). Tenant-3 tool calls now require HUX release for real.
Remaining: real-traffic validation of the approvals UX on tenant-3, then
partition 3 -> 0 to take flags+enforcement fleet-wide; HUX-12 needs the
Vault evidence key (runbook above); Worker integration separate.
## Enforcement live on the canary (2026-08-24 ~09:35 UTC)
Canary at 4a6d4fdf+17037773: HUX_TOOL_ENFORCEMENT=1 and HUX_RUNTIME_ENABLED=1
verified in the live hermes container; identity hashes unchanged across a
third roll (8a1c474d/c4ebe353); /healthz ok with retention healthy; all five
containers ready, zero restarts. The earlier hux exit-137s were transient
probe kills under roll churn (threaded server, no OOM/liveness events, clean
logs) — bump the hux exec probe timeoutSeconds 3->5 with the
partition-widening commit. Voice fixes pushed as 91cfb901 (continuous mic,
barge stitching, 1.15x TTS); build 22 queued — its digest bump rolls the
fleet including this canary.
## FLEET LIVE (2026-08-24 ~12:55 UTC) — HUX-01..11 live on all four tenants
Widening commit 5fc3e870 applied, Flux Ready=True. Verification 39/39:
every tenant 5/5 ready on webui build-22
(git-91cfb901...-build-22-release@sha256:2313d0d5...) for webui + hux +
producer; agent digest unchanged everywhere; hux /healthz ok per pod;
HUX_TOOL_ENFORCEMENT=1 fleet-wide; HUX_IMAGE_TAG env now consistent with the
image (block-style setters); four DISTINCT HMAC subjects (per-pod isolation
proven); public /healthz 200; zero container restarts under the 5s probe
budgets (the 2s exec budget had produced 405 readiness timeouts in 135min and
stalled the digest roll — root-caused and fixed). Voice fixes (continuous
mic, barge stitching, 1.15x TTS) shipped in the same image.
Remaining, deliberately gated:
- HUX-12 enablement: Vault evidence key + scope ConfigMap (runbook above);
everything else is live and fail-closed.
- Worker (worker.bstein.dev): separate integration + canary, not started —
cross-surface continuity is NOT claimed.
- Real-traffic approvals-UX feedback from the operator; rollback = one
revert of 5fc3e870 (re-partition) and/or HUX_TOOL_ENFORCEMENT=0.
## Enforcement rolled back to observe-only (2026-08-24 ~13:3x UTC, 1793903f)
Live traffic on the enforced fleet showed the first defaults blocking core
assistant faculties (skills listing and the sandboxed Python fell into the
unknown->external_side_effect always-ask bucket, browsing was deny-by-
default, and no approval prompt surfaced in the chat flow). Fleet rolled to
HUX_TOOL_ENFORCEMENT=0 with a corrected capability map (python_sandbox =
internal shell; skills/todo/clarify/vision = reads; browser/web = network
medium; image_gen = artifact_write via the trusted broker; unknown tools
stay fail-closed). Verified live: all tenants 5/5, enf=0 everywhere.
Re-enforcement checklist (in order):
1. Approvals UX must surface prompts inline in the chat/voice flow.
2. Default grants reviewed against the corrected capability map (network
under safe autonomy should ask, not deny, for the chat surface).
3. Canary re-enable on one ordinal first (partition), then fleet.
## Round-2 fleet live (2026-08-24 ~14:4x UTC) — build 23 everywhere
All four tenants verified on
git-043aa9ee...-build-23-release@sha256:b3b5830d... (webui + hux + producer;
agent digest unchanged; per-pod hux /healthz ok; HUX_IMAGE_TAG env consistent
with the image — the block-style setters were rewritten by Flux this cycle,
confirming the flow-mapping fix; four distinct HMAC subjects; public
/healthz 200; enforcement observe-only BY DESIGN pending the approvals-UX
work). One transient registry 404 during tenant-1's pull (traefik-VIP flake
class) cleared by kubelet retry in ~2 min — manifests verified intact.
Build 23 contents: voice clipping fix (young-utterance endpoint hold,
speculate min-speech, unconditional resume, capped noise floor + capture-
continuity probe), always-stitch within 20s of a voice barge, HUX drawer
theme-token bridge (side-panel dark-box fix), full-screen conversation mode
(orb, live captions, mute/exit, a11y), renderer block-style HUX binding.
Definitive remaining list: HUX-12 Vault key + scope ConfigMap (operator
runbook above); approvals-UX inline prompts then re-enforcement via canary;
Worker (worker.bstein.dev) integration as its own lane; titan-iac main CI
baseline confirmation post node-skip hardening.
## Build 24 fleet-live + cluster read + worker staged (2026-08-24 ~16:xx UTC)
Build 24 (d041f1d1 -> sha256:1ea5b4ad, promote 04f060db) on all four
tenants + the worker agent. Fleet verify 34/34 meaningful (enforcement
observe-only by design; Flux Ready=True after reconcile). Worker gates 6/6
with identity PERSISTED across two rolls (hashes 7fa1c7d3/532b9745 unchanged)
and the auth matrix exact (unauth/badkey/wrongslot 401, worker caps=HUX-11).
Shipped this cycle:
- Voice round 3: caption body-only extraction, dynamic endpointing, both
captions touch-scrollable, errors never spoken + resyncCapture, workspace
toggle in the sidebar rail, the false 'session unavailable' toast (409 from
a fail-closed profile-alias race - fixed at the cause), barge cut-point
marker, full language routing, speaking<->thinking cycling.
- Approvals UX: inline pending-approval dock above the composer; the default
grant matrix now asks (not denies) for network/web_search below autonomous,
SO-39 intact. Re-enforcement checklist items 1+2 done code-side.
- cluster_read: read-only Kubernetes visibility LIVE and verified in-chat
(pods/nodes/flux read; secrets refused; RBAC excludes Secrets). Required a
tenant egress fix to the real apiserver endpoints (192.168.22.11-13:6443,
which the except-block dropped post-DNAT) and a large-node-listing handler
hardening (ed0bc30d).
- Worker staged inert-HUX (foundation only, enforcement 0) - activation is a
reviewed one-line flip per WORKER-PLAN.md.
Transient registry 404 during tenant-3's pull recurred (3rd time; VIP/registry
flake class) and self-cleared - worth a registry-reliability follow-up.
Remaining: HUX-12 Vault key (operator); re-enforcement canary (approvals UX
now exists - operator tries the prompts, then canary re-enable per checklist);
worker HUX activation flip (WORKER-PLAN TODO 2); cluster_read handler
hardening activates on the next pod roll.
## Final webui build-29 live + verified (2026-08-24)
All tenants on webui build-29 (sha256:6887c2ad, source 4d93ef5a), verified in
the served bundle: Workspace toggle in the nav (desktop rail + mobile
titlebar via navIconToggle/titlebarHost, no composer-obstructing floating
pill - fixed the mobile regression that pushed the input controls off
screen); conversation orb watermark = the Hermes CHARACTER avatar
(hermes-agent-192.png), staff SVG removed; 'hands-free' renamed to
'Conversation mode'; reply-language routing density-gated
(resolveReplyLanguage) so English no longer gets a foreign voice. Also
carries: interim-ack tail flush, natural thinking fillers, one unified audio
sink + output-device selector, the 'Something went wrong' error-veto, and
the cluster_read large-listing fix.
STT: large-v3-turbo is live for accuracy (already correcting proper nouns);
the beam-2 + name-prompt tuning is pending a successful hermes-voice-image
(stt) build - the build keeps failing on an IPv6 route to azureedge while
downloading the Whisper models (a more persistent flake than the ghcr.io
DNS one). Durable follow-up recommended: mirror the ghcr.io webui base image
and the Whisper models into registry.bstein.dev so builds stop depending on
external DNS/IPv6 (3 webui + 3 stt builds lost to these flakes).
Gated on the operator: HUX-12 Vault evidence key; re-enabling enforcement
(approvals UX is live); worker HUX activation flip.