atlas-iac/docs/hux/HANDOFF.md
jenkins 18b980d6fa hermes(hux): expose conversation privacy state for the agent hook
GET /hux/v1/conversations/{id}/privacy reports forgotten, memory_disabled,
topics, mode and memory_writes_allowed; the worker hook's memory gate reads it
and fails closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNPhwu2bsaRNg3DETSAZoM
2026-08-24 00:54:34 -03:00

11 KiB
Raw Blame History

hermes-next: Claude backend handoff ledger

Branch feature/hermes-next-hux (origin, pushed after every card); draft PR #55 atlas/titan-iac#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

  • 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 47a9fd83 (contract freeze 1.0.0), 7cf9a714 (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 1cb6f07c 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 1cb6f07c 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 1cb6f07c 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 ada74ce0 (+ 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 ada74ce0 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 b3de70ba 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 b3de70ba 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 124206b7 (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 dd80e4bd — policy writes and allow grants are human-surface only
F2 high worker trust could read every tenant record (SO-08) dd80e4bdflags.WORKER_ROUTES allowlist enforced in Router.dispatch; unexpected exceptions become audited 500 records
F3 high memory edit skipped topic/sensitivity/private-mode gates 4417475d — one _classify path for proposals and edits
F4 high a session approval for an external effect released unrelated later effects dd80e4bd — 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 4417475d — seq = max(checkpoint, ledger tail + 1) under the lock
F6 medium gate ignored budget exhaustion dd80e4bd
F7 medium stale unconditional write could resurrect a forgotten memory 4417475d — re-read under lock, always write with the loaded revision
F8 medium any caller could assert process_registry_empty; failed receipts were sticky dd80e4bd — gateway (worker trust) only; failed receipts supersedable
F9 medium secrets stored verbatim in titles/passages/claims/notebooks; forget left the title 4417475d — scrub applied; forget blanks the document. Artifact bodies stay verbatim (user-owned) but are forced restricted and audited (this commit)
F10 medium SO-46/48/53 claimed but absent threat model amended (5acf640d): tracked as open, not claimed
F11 low stored receipts carry revision the schema forbade dd80e4bd — optional revision on the receipt
F12 low capabilities advertised routes without handlers dd80e4bd — 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) dd80e4bd + 4417475d

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 532add3a
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 proceedafter_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.
  • 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).