HUX-11 contract freeze: identity/capabilities/manifest/error records, event turn and idempotency and delegation/side-effect kinds, memory no-store/supersedes/ retrieval removal, revisions for optimistic concurrency, artifact access, budget scope/spend/subagents and external side-effect gating, notebook notes and dedupe keys. ADR-0001 records the wire and compatibility rules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNPhwu2bsaRNg3DETSAZoM
70 lines
4.5 KiB
Markdown
70 lines
4.5 KiB
Markdown
# ADR-0001: freeze the hux.v1 contract at 1.0.0
|
||
|
||
Status: accepted, 2026-08-24. Owners: Claude (backend), Codex (integration).
|
||
|
||
## Decision
|
||
|
||
`services/hermes/contracts/hux/` is frozen as contract version `1.0.0`
|
||
(`VERSION`, `flags.json.contract_version`). It is the only shape any HUX
|
||
surface (Chat, Worker, Telegram, voice) codes against. The Python reference
|
||
implementation lives in `dockerfiles/hermes-hux-foundation/hux/`:
|
||
`contracts.py` validates records, `rules.py` holds the governance rules the
|
||
schemas cannot express.
|
||
|
||
Records carry their own version in `schema` (`hux.event.v1`). Wire fixtures
|
||
under `services/hermes/contracts/hux/examples/` are validated by
|
||
`testing/tests/test_hermes_hux_contract_schemas.py` and are the exchange
|
||
format with Codex: a change to a fixture is a change to the contract.
|
||
|
||
## What 1.0.0 covers
|
||
|
||
| Requirement | Where |
|
||
|---|---|
|
||
| Tenant and surface identity | `common.identity` (tenant_slot, hashed subject, surface, trust: router/relay/worker) — carried on every stored record that a caller can create |
|
||
| Capability negotiation, default-off flags | `identity.capabilities` (`GET /hux/v1/capabilities`), `flags.json`; a card is enabled only when its dependency chain is enabled (`rules.flag_enabled`) |
|
||
| Redacted activity events | `event`: `seq` (monotonic per conversation), `turn`, `correlation_id`, `idempotency_key`, replay by `after_seq`, kinds for decisions, delegation, failure/completion, cancellation, budget and side-effect gates; `detail` is allowlisted per kind and scrubbed |
|
||
| Memory | `memory`: provenance, `source`, `reason`, `sensitivity`, `topic`, `ttl`, `approval_mode` incl. `no_store`, `status` incl. `no_store`, edit via `supersedes`, delete via `forgotten`, retrieval removal via `retrievable=false`, revisioned |
|
||
| Projects, lineage, artifacts, versions, diffs, authorization | `project`, `conversation.branch`, `artifact.versions[]` immutable with `content_ref` hash, `diff_from`, `lineage`, `promotion`, `access`, `revision` |
|
||
| Autonomy | `permission.policy` (levels, grants, budgets: tokens/tool calls/wall clock/spend/subagents/delegations/scope), `approval` with `request.external`, `cancellation_receipt`, `budget_state` |
|
||
| Research | `citation.source/passage/citation/notebook` with classification, support verdict, notes, assumptions, unresolved questions, `dedupe_key` |
|
||
| Additive migrations, rollback readers | `identity.manifest` (`data_layout_version`, `min_reader_contract_version`); rules in "Compatibility" below |
|
||
|
||
## Wire conventions
|
||
|
||
- Identity is asserted by the trusted hop, never by the client body: the
|
||
chat router sets `X-Hermes-Tenant-Identity: slot-N`, `X-Hux-Subject:
|
||
usr_<hash>`, `X-Hux-Surface: chat`; the Telegram relay adds the relay key
|
||
and `trust: relay`; the Worker uses `trust: worker`. Requests without a
|
||
complete, well-formed identity are `401 unauthorized`. The service must
|
||
only be reachable from those hops (NetworkPolicy is Codex's to enforce).
|
||
- Mutations of revisioned records send `If-Match: <revision>`; a mismatch is
|
||
`409 conflict` with `hux.error.v1`. Creates accept `Idempotency-Key`; a
|
||
repeat returns the original record with `200` instead of a duplicate.
|
||
- Lists return `{"items": [...], "next": <cursor|null>}`. Event pages use
|
||
`after_seq`; the SSE stream sets `id:` to `seq` so `Last-Event-ID`
|
||
resumes exactly.
|
||
- Errors are always `hux.error.v1`. A disabled card answers `404 flag_off`
|
||
so an old client and a flag-off server look identical.
|
||
- Every read and mutation writes a `common.audit_outcome`.
|
||
|
||
## Compatibility rules
|
||
|
||
1. Within `1.x`: only add optional fields, enum values or new record kinds.
|
||
Never rename, remove, change a type, or make an optional field required.
|
||
2. Readers ignore unknown fields and unknown enum values they do not need;
|
||
a reader that needs a new enum value must check `contract_version`.
|
||
3. On-disk data is append-only per family; `data_layout_version` bumps only
|
||
for additive layout changes, and `min_reader_contract_version` says the
|
||
oldest service that can still read it. Rolling the service back to any
|
||
version ≥ `min_reader_contract_version` leaves data readable.
|
||
4. Anything outside 1–3 is `hux.v2`, served beside v1 until every surface
|
||
has moved.
|
||
|
||
## Consequences
|
||
|
||
- Codex codes UI against the fixtures, not against the running service.
|
||
- Claude's implementation cards (HUX-01/02/03/04/05/08/10/11) may add
|
||
optional fields but must ship the fixture change in the same commit.
|
||
- If Codex needs a shared-interface change, it lands as a `1.(x+1).0`
|
||
revision of the schemas and fixtures here, never as an informal edit.
|