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
11 KiB
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 aspython -m hux.serverinside eachhermes-chat-tenantpod (and the Worker) on127.0.0.1:8790. Image, manifest, NetworkPolicy and Flux wiring are Codex's (SO-01, SO-02, SO-45: separate uid,/opt/data/huxmode 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, optionalX-Hux-Trust: relay|worker+X-Hux-Relay-Key. The router must strip any inboundX-Hux-*before setting its own. - First client call:
GET /hux/v1/capabilities→ which cards/routes are on. Disabled or unknown →404(flag_offvsnot_foundonly in the body). - Mutations:
If-Match: <revision>on revisioned records (409 on mismatch);Idempotency-Keyon creates. Errors arehux.error.v1. - Fixtures Codex codes against:
services/hermes/contracts/hux/examples/*.json(validated in CI bytest_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 99–100%). 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) | dd80e4bd — flags.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 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.
- Retention scheduler:
privacy.run_retentionis on-demand; Codex decides cron vs sidecar. - Search over
message_text; artifact sharing (shared_readonly). testing/quality_contract.jsonline-limit globs / managed modules for the two new package paths (Codex's file).