hermes(hux): freeze hux.v1 contract 1.0.0 and move reference modules into the foundation package

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
This commit is contained in:
jenkins 2026-08-24 00:04:37 -03:00
parent af5ee4697b
commit 47a9fd8354
32 changed files with 2442 additions and 300 deletions

View File

@ -10,13 +10,18 @@ loudly rather than silently passing.
from __future__ import annotations
import json
import os
import re
from pathlib import Path
from typing import Any
CONTRACT_DIR = Path(__file__).resolve().parents[1] / "contracts" / "hux"
CONTRACT_DIR = Path(
os.environ.get("HUX_CONTRACT_DIR")
or Path(__file__).resolve().parents[3] / "services" / "hermes" / "contracts" / "hux"
)
SCHEMA_FILES = (
"common.schema.json",
"identity.schema.json",
"event.schema.json",
"memory.schema.json",
"project.schema.json",

View File

@ -13,7 +13,7 @@ import os
from datetime import datetime, timedelta, timezone
from typing import Any
from hux_contracts import load_flags
from hux.contracts import load_flags
EFFORT_ORDER = ("low", "medium", "high", "xhigh")
@ -44,11 +44,12 @@ RELEASE_EVIDENCE: dict[str, tuple[str, ...]] = {
CAPABILITIES = (
"read_files", "write_files", "shell", "network", "web_search", "send_message",
"memory_write", "artifact_write", "spend_tokens", "delegate", "deploy",
"memory_write", "artifact_write", "spend_tokens", "delegate", "deploy", "external_side_effect",
)
_READ_ONLY = frozenset({"read_files", "web_search", "spend_tokens"})
_MUTATING = frozenset({"write_files", "shell", "send_message", "memory_write", "artifact_write", "delegate"})
_EXTERNAL = frozenset({"network", "deploy"})
_ALWAYS_ASK = frozenset({"deploy", "external_side_effect"})
MODE_CATALOG: dict[str, dict[str, Any]] = {
"fast": {
@ -140,12 +141,12 @@ def memory_policy_violations(entry: dict[str, Any]) -> list[str]:
def default_capability_matrix() -> dict[str, dict[str, str]]:
"""Autonomy level -> capability -> allow|ask|deny. Deploy always asks."""
"""Autonomy level -> capability -> allow|ask|deny. Deploy and external side effects always ask."""
matrix: dict[str, dict[str, str]] = {}
for level in ("ask_first", "safe", "autonomous"):
row: dict[str, str] = {}
for capability in CAPABILITIES:
if capability == "deploy":
if capability in _ALWAYS_ASK:
row[capability] = "ask"
elif capability in _READ_ONLY:
row[capability] = "allow"
@ -171,7 +172,7 @@ def effective_decision(policy: dict[str, Any], capability: str, now: datetime |
if grant["decision"] == "deny":
return "deny"
decision = grant["decision"]
if capability == "deploy" and decision == "allow":
if capability in _ALWAYS_ASK and decision == "allow":
return "ask"
return decision

View File

@ -10,9 +10,12 @@ Schemas live in `services/hermes/contracts/hux/` as JSON Schema 2020-12 with
one worked example per record under `examples/`. The rules a schema cannot
express (state machines, capability matrix, mode catalog, privacy defaults,
suggestion gating, flag dependencies) live in
`services/hermes/scripts/hux_policy.py`; `hux_contracts.py` validates records
without external packages. `testing/tests/test_hermes_hux_contracts.py` keeps
schemas, examples, rules and the live Switchyard catalog in agreement.
`dockerfiles/hermes-hux-foundation/hux/rules.py`; `hux/contracts.py`
validates records without external packages.
`testing/tests/test_hermes_hux_contract_schemas.py` keeps schemas, examples,
rules and the live Switchyard catalog in agreement. The contract is frozen at
`1.0.0` (see `docs/hux/ADR-0001-hux-v1-contract-freeze.md` for identity
headers, `If-Match`, idempotency and compatibility rules).
## Where the backend lives
@ -51,11 +54,12 @@ tenant. Responses are the records below, wrapped as `{"items": [...],
| Area | Routes | Record |
|---|---|---|
| Foundation (HUX-11) | `GET /hux/v1/capabilities`, `GET /hux/v1/manifest` | `hux.capabilities.v1`, `hux.manifest.v1`, errors as `hux.error.v1` |
| Events (HUX-01) | `GET /hux/v1/conversations/{id}/events?after_seq=N` (JSON), `GET .../events/stream` (SSE, `id:` = seq) | `hux.event.v1` |
| Memory (HUX-02) | `GET/POST /hux/v1/memory`, `POST /hux/v1/memory/{id}/{approve,reject,forget}`, `GET /hux/v1/memory/export` | `hux.memory.v1` |
| Projects (HUX-03) | `GET/POST/PATCH /hux/v1/projects`, `GET/PATCH /hux/v1/conversations`, `POST /hux/v1/conversations/{id}/branch`, `GET /hux/v1/search?q=` | `hux.project.v1`, `hux.conversation.v1` |
| Artifacts (HUX-04) | `GET/POST /hux/v1/artifacts`, `POST /hux/v1/artifacts/{id}/versions`, `GET .../versions/{n}/diff?from=`, `POST .../promote` | `hux.artifact.v1` |
| Autonomy (HUX-05) | `GET/PUT /hux/v1/policy?scope=`, `GET /hux/v1/approvals`, `POST /hux/v1/approvals/{id}` (`once|session|always|deny`, the gateway's own choices), `POST /hux/v1/runs/{id}/stop` returns the receipt | `hux.policy.v1`, `hux.approval.v1`, `hux.cancel_receipt.v1` |
| Autonomy (HUX-05) | `GET/PUT /hux/v1/policy?scope=`, `GET /hux/v1/approvals`, `POST /hux/v1/approvals/{id}` (`once|session|always|deny`, the gateway's own choices), `POST /hux/v1/runs/{id}/stop` returns the receipt, `GET /hux/v1/runs/{id}/budget` | `hux.policy.v1`, `hux.approval.v1`, `hux.cancel_receipt.v1`, `hux.budget_state.v1` |
| Modes (HUX-06) | `GET /hux/v1/modes`, `PUT /hux/v1/conversations/{id}/mode` | `hux.mode.v1` |
| Research (HUX-08) | `GET /hux/v1/messages/{id}/citations`, `GET /hux/v1/sources/{id}`, `GET/PATCH /hux/v1/notebooks/{id}` | `hux.source.v1`, `hux.passage.v1`, `hux.citation.v1`, `hux.research_notebook.v1` |
| Onboarding (HUX-09) | `GET /hux/v1/suggestions?context=`, `POST /hux/v1/suggestions/{id}/{dismiss,never,acted}` | `hux.suggestion.v1`, `hux.suggestion_state.v1` |

View File

@ -0,0 +1,69 @@
# 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 13 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.

View File

@ -0,0 +1 @@
1.0.0

View File

@ -5,65 +5,192 @@
"description": "Typed, versioned output (HUX-04). Content lives behind content_ref; versions are immutable and diffable; lineage records what an artifact was derived from; promotion copies a version into a project.",
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "owner", "type", "title", "current_version", "versions", "sensitivity", "created_at", "updated_at"],
"required": [
"schema",
"id",
"owner",
"type",
"title",
"current_version",
"versions",
"sensitivity",
"created_at",
"updated_at",
"revision",
"access"
],
"properties": {
"schema": {"const": "hux.artifact.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"owner": {"$ref": "common.schema.json#/$defs/user_ref"},
"conversation_id": {"$ref": "common.schema.json#/$defs/id"},
"project_id": {"$ref": "common.schema.json#/$defs/id"},
"type": {"type": "string", "enum": ["markdown", "code", "html", "svg", "image", "json", "csv", "document", "audio"]},
"language": {"type": "string", "maxLength": 40},
"title": {"type": "string", "minLength": 1, "maxLength": 200},
"current_version": {"type": "integer", "minimum": 1},
"schema": {
"const": "hux.artifact.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"owner": {
"$ref": "common.schema.json#/$defs/user_ref"
},
"conversation_id": {
"$ref": "common.schema.json#/$defs/id"
},
"project_id": {
"$ref": "common.schema.json#/$defs/id"
},
"type": {
"type": "string",
"enum": [
"markdown",
"code",
"html",
"svg",
"image",
"json",
"csv",
"document",
"audio"
]
},
"language": {
"type": "string",
"maxLength": 40
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"current_version": {
"type": "integer",
"minimum": 1
},
"versions": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["version", "created_at", "created_by", "content_ref"],
"required": [
"version",
"created_at",
"created_by",
"content_ref"
],
"properties": {
"version": {"type": "integer", "minimum": 1},
"created_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"created_by": {"$ref": "common.schema.json#/$defs/actor"},
"message_id": {"type": "string", "maxLength": 120},
"version": {
"type": "integer",
"minimum": 1
},
"created_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"created_by": {
"$ref": "common.schema.json#/$defs/actor"
},
"message_id": {
"type": "string",
"maxLength": 120
},
"content_ref": {
"type": "object",
"additionalProperties": false,
"required": ["hash", "bytes", "mime"],
"required": [
"hash",
"bytes",
"mime"
],
"properties": {
"hash": {"$ref": "common.schema.json#/$defs/sha256"},
"bytes": {"type": "integer", "minimum": 0},
"mime": {"type": "string", "maxLength": 120}
"hash": {
"$ref": "common.schema.json#/$defs/sha256"
},
"bytes": {
"type": "integer",
"minimum": 0
},
"mime": {
"type": "string",
"maxLength": 120
}
}
},
"diff_from": {"type": "integer", "minimum": 1},
"diff_from": {
"type": "integer",
"minimum": 1
},
"lineage": {
"type": "object",
"additionalProperties": false,
"required": ["artifact_id", "version"],
"required": [
"artifact_id",
"version"
],
"properties": {
"artifact_id": {"$ref": "common.schema.json#/$defs/id"},
"version": {"type": "integer", "minimum": 1}
"artifact_id": {
"$ref": "common.schema.json#/$defs/id"
},
"version": {
"type": "integer",
"minimum": 1
}
}
},
"note": {"type": "string", "maxLength": 200}
"note": {
"type": "string",
"maxLength": 200
}
}
}
},
"promotion": {
"type": "object",
"additionalProperties": false,
"required": ["project_id", "version", "at"],
"required": [
"project_id",
"version",
"at"
],
"properties": {
"project_id": {"$ref": "common.schema.json#/$defs/id"},
"version": {"type": "integer", "minimum": 1},
"at": {"$ref": "common.schema.json#/$defs/timestamp"}
"project_id": {
"$ref": "common.schema.json#/$defs/id"
},
"version": {
"type": "integer",
"minimum": 1
},
"at": {
"$ref": "common.schema.json#/$defs/timestamp"
}
}
},
"sensitivity": {"$ref": "common.schema.json#/$defs/sensitivity"},
"created_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"updated_at": {"$ref": "common.schema.json#/$defs/timestamp"}
"sensitivity": {
"$ref": "common.schema.json#/$defs/sensitivity"
},
"created_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"updated_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"revision": {
"$ref": "common.schema.json#/$defs/revision"
},
"access": {
"type": "object",
"description": "Owner-only unless a share token is minted; shares are read-only and expire.",
"additionalProperties": false,
"required": [
"mode"
],
"properties": {
"mode": {
"type": "string",
"enum": [
"owner",
"shared_readonly"
]
},
"share_expires_at": {
"$ref": "common.schema.json#/$defs/timestamp"
}
}
}
}
}

View File

@ -7,82 +7,320 @@
"source": {
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "kind", "title", "classification", "retrieved_at"],
"required": [
"schema",
"id",
"kind",
"title",
"classification",
"retrieved_at"
],
"properties": {
"schema": {"const": "hux.source.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"kind": {"type": "string", "enum": ["web", "document", "artifact", "memory", "tool_output", "dataset"]},
"uri": {"type": "string", "maxLength": 2000},
"title": {"type": "string", "minLength": 1, "maxLength": 300},
"publisher": {"type": "string", "maxLength": 200},
"published_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"retrieved_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"classification": {"type": "string", "enum": ["primary", "secondary", "unknown"]},
"content_hash": {"$ref": "common.schema.json#/$defs/sha256"},
"provenance": {"$ref": "common.schema.json#/$defs/provenance"}
"schema": {
"const": "hux.source.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"kind": {
"type": "string",
"enum": [
"web",
"document",
"artifact",
"memory",
"tool_output",
"dataset"
]
},
"uri": {
"type": "string",
"maxLength": 2000
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 300
},
"publisher": {
"type": "string",
"maxLength": 200
},
"published_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"retrieved_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"classification": {
"type": "string",
"enum": [
"primary",
"secondary",
"unknown"
]
},
"content_hash": {
"$ref": "common.schema.json#/$defs/sha256"
},
"provenance": {
"$ref": "common.schema.json#/$defs/provenance"
},
"dedupe_key": {
"type": "string",
"description": "Stable key used to merge duplicates: sha256 of normalised uri (source), of source_id+text hash (passage), of message_id+claim+passages (citation).",
"pattern": "^sha256:[0-9a-f]{64}$"
}
}
},
"passage": {
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "source_id", "text", "hash"],
"required": [
"schema",
"id",
"source_id",
"text",
"hash"
],
"properties": {
"schema": {"const": "hux.passage.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"source_id": {"$ref": "common.schema.json#/$defs/id"},
"text": {"type": "string", "minLength": 1, "maxLength": 4000},
"hash": {"$ref": "common.schema.json#/$defs/sha256"},
"schema": {
"const": "hux.passage.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"source_id": {
"$ref": "common.schema.json#/$defs/id"
},
"text": {
"type": "string",
"minLength": 1,
"maxLength": 4000
},
"hash": {
"$ref": "common.schema.json#/$defs/sha256"
},
"locator": {
"type": "object",
"additionalProperties": false,
"properties": {
"page": {"type": "integer", "minimum": 1},
"selector": {"type": "string", "maxLength": 500},
"line_start": {"type": "integer", "minimum": 1},
"line_end": {"type": "integer", "minimum": 1},
"char_start": {"type": "integer", "minimum": 0},
"char_end": {"type": "integer", "minimum": 0}
"page": {
"type": "integer",
"minimum": 1
},
"selector": {
"type": "string",
"maxLength": 500
},
"line_start": {
"type": "integer",
"minimum": 1
},
"line_end": {
"type": "integer",
"minimum": 1
},
"char_start": {
"type": "integer",
"minimum": 0
},
"char_end": {
"type": "integer",
"minimum": 0
}
}
},
"dedupe_key": {
"type": "string",
"description": "Stable key used to merge duplicates: sha256 of normalised uri (source), of source_id+text hash (passage), of message_id+claim+passages (citation).",
"pattern": "^sha256:[0-9a-f]{64}$"
}
}
},
"citation": {
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "message_id", "claim", "passage_ids", "support"],
"required": [
"schema",
"id",
"message_id",
"claim",
"passage_ids",
"support"
],
"properties": {
"schema": {"const": "hux.citation.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"message_id": {"type": "string", "minLength": 1, "maxLength": 120},
"claim": {"type": "string", "minLength": 1, "maxLength": 1000},
"passage_ids": {"type": "array", "minItems": 1, "maxItems": 32, "uniqueItems": true, "items": {"$ref": "common.schema.json#/$defs/id"}},
"support": {"type": "string", "enum": ["supports", "partially_supports", "contradicts", "unverified"]},
"note": {"type": "string", "maxLength": 500}
"schema": {
"const": "hux.citation.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"message_id": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"claim": {
"type": "string",
"minLength": 1,
"maxLength": 1000
},
"passage_ids": {
"type": "array",
"minItems": 1,
"maxItems": 32,
"uniqueItems": true,
"items": {
"$ref": "common.schema.json#/$defs/id"
}
},
"support": {
"type": "string",
"enum": [
"supports",
"partially_supports",
"contradicts",
"unverified"
]
},
"note": {
"type": "string",
"maxLength": 500
},
"dedupe_key": {
"type": "string",
"description": "Stable key used to merge duplicates: sha256 of normalised uri (source), of source_id+text hash (passage), of message_id+claim+passages (citation).",
"pattern": "^sha256:[0-9a-f]{64}$"
}
}
},
"notebook": {
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "conversation_id", "question", "status", "source_ids", "passage_ids", "citation_ids", "assumptions", "unresolved_questions", "updated_at"],
"required": [
"schema",
"id",
"conversation_id",
"question",
"status",
"source_ids",
"passage_ids",
"citation_ids",
"assumptions",
"unresolved_questions",
"updated_at",
"notes",
"revision"
],
"properties": {
"schema": {"const": "hux.research_notebook.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"conversation_id": {"$ref": "common.schema.json#/$defs/id"},
"question": {"type": "string", "minLength": 1, "maxLength": 1000},
"status": {"type": "string", "enum": ["open", "answered", "abandoned"]},
"source_ids": {"type": "array", "uniqueItems": true, "items": {"$ref": "common.schema.json#/$defs/id"}},
"passage_ids": {"type": "array", "uniqueItems": true, "items": {"$ref": "common.schema.json#/$defs/id"}},
"citation_ids": {"type": "array", "uniqueItems": true, "items": {"$ref": "common.schema.json#/$defs/id"}},
"assumptions": {"type": "array", "maxItems": 64, "items": {"type": "string", "minLength": 1, "maxLength": 500}},
"unresolved_questions": {"type": "array", "maxItems": 64, "items": {"type": "string", "minLength": 1, "maxLength": 500}},
"updated_at": {"$ref": "common.schema.json#/$defs/timestamp"}
"schema": {
"const": "hux.research_notebook.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"conversation_id": {
"$ref": "common.schema.json#/$defs/id"
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 1000
},
"status": {
"type": "string",
"enum": [
"open",
"answered",
"abandoned"
]
},
"source_ids": {
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "common.schema.json#/$defs/id"
}
},
"passage_ids": {
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "common.schema.json#/$defs/id"
}
},
"citation_ids": {
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "common.schema.json#/$defs/id"
}
},
"assumptions": {
"type": "array",
"maxItems": 64,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
}
},
"unresolved_questions": {
"type": "array",
"maxItems": 64,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
}
},
"updated_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"notes": {
"type": "array",
"maxItems": 128,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"at",
"text"
],
"properties": {
"at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"text": {
"type": "string",
"minLength": 1,
"maxLength": 2000
},
"source_id": {
"$ref": "common.schema.json#/$defs/id"
}
}
}
},
"revision": {
"$ref": "common.schema.json#/$defs/revision"
}
}
}
},
"oneOf": [
{"$ref": "#/$defs/source"},
{"$ref": "#/$defs/passage"},
{"$ref": "#/$defs/citation"},
{"$ref": "#/$defs/notebook"}
{
"$ref": "#/$defs/source"
},
{
"$ref": "#/$defs/passage"
},
{
"$ref": "#/$defs/citation"
},
{
"$ref": "#/$defs/notebook"
}
]
}

View File

@ -25,98 +25,317 @@
},
"surface": {
"type": "string",
"enum": ["chat", "worker", "telegram", "voice", "api"]
"enum": [
"chat",
"worker",
"telegram",
"voice",
"api"
]
},
"provider": {
"type": "string",
"description": "Provider class, never a vendor model name. Switchyard resolves the class to a concrete target.",
"enum": ["codex", "claude", "local"]
"enum": [
"codex",
"claude",
"local"
]
},
"effort": {
"type": "string",
"enum": ["low", "medium", "high", "xhigh"]
"enum": [
"low",
"medium",
"high",
"xhigh"
]
},
"sensitivity": {
"type": "string",
"description": "public: safe to show anywhere; personal: user-owned but not sensitive; sensitive: health/finance/legal/relationships; restricted: credentials, minors, biometric, location traces.",
"enum": ["public", "personal", "sensitive", "restricted"]
"enum": [
"public",
"personal",
"sensitive",
"restricted"
]
},
"redaction": {
"type": "object",
"additionalProperties": false,
"required": ["level"],
"required": [
"level"
],
"properties": {
"level": {"type": "string", "enum": ["none", "partial", "full"]},
"reason": {"type": "string", "maxLength": 200}
"level": {
"type": "string",
"enum": [
"none",
"partial",
"full"
]
},
"reason": {
"type": "string",
"maxLength": 200
}
}
},
"actor": {
"type": "object",
"additionalProperties": false,
"required": ["type", "id"],
"required": [
"type",
"id"
],
"properties": {
"type": {"type": "string", "enum": ["user", "assistant", "tool", "system", "operator"]},
"id": {"type": "string", "minLength": 1, "maxLength": 120},
"display": {"type": "string", "maxLength": 120}
"type": {
"type": "string",
"enum": [
"user",
"assistant",
"tool",
"system",
"operator"
]
},
"id": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"display": {
"type": "string",
"maxLength": 120
}
}
},
"route": {
"type": "object",
"description": "What was asked of Switchyard and what it resolved. requested is a friendly mode or a route id; resolved_target is the Switchyard target name.",
"additionalProperties": false,
"required": ["requested"],
"required": [
"requested"
],
"properties": {
"requested": {"type": "string", "minLength": 1, "maxLength": 120},
"resolved_target": {"type": "string", "maxLength": 120},
"provider": {"$ref": "#/$defs/provider"},
"effort": {"$ref": "#/$defs/effort"}
"requested": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"resolved_target": {
"type": "string",
"maxLength": 120
},
"provider": {
"$ref": "#/$defs/provider"
},
"effort": {
"$ref": "#/$defs/effort"
}
}
},
"build": {
"type": "object",
"additionalProperties": false,
"properties": {
"commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
"image_digest": {"$ref": "#/$defs/sha256"}
"commit": {
"type": "string",
"pattern": "^[0-9a-f]{40}$"
},
"image_digest": {
"$ref": "#/$defs/sha256"
}
}
},
"provenance": {
"type": "object",
"description": "Who produced a record, on which surface, under which session/run, through which route, from which build.",
"additionalProperties": false,
"required": ["surface", "actor", "recorded_at"],
"required": [
"surface",
"actor",
"recorded_at"
],
"properties": {
"surface": {"$ref": "#/$defs/surface"},
"actor": {"$ref": "#/$defs/actor"},
"recorded_at": {"$ref": "#/$defs/timestamp"},
"session_id": {"type": "string", "maxLength": 120},
"conversation_id": {"$ref": "#/$defs/id"},
"message_id": {"type": "string", "maxLength": 120},
"run_id": {"type": "string", "maxLength": 120},
"route": {"$ref": "#/$defs/route"},
"build": {"$ref": "#/$defs/build"}
"surface": {
"$ref": "#/$defs/surface"
},
"actor": {
"$ref": "#/$defs/actor"
},
"recorded_at": {
"$ref": "#/$defs/timestamp"
},
"session_id": {
"type": "string",
"maxLength": 120
},
"conversation_id": {
"$ref": "#/$defs/id"
},
"message_id": {
"type": "string",
"maxLength": 120
},
"run_id": {
"type": "string",
"maxLength": 120
},
"route": {
"$ref": "#/$defs/route"
},
"build": {
"$ref": "#/$defs/build"
}
}
},
"evidence_ref": {
"type": "object",
"description": "Pointer to the thing that justifies a record. Never inline the payload here; the UI expands it through the owning API.",
"additionalProperties": false,
"required": ["kind", "id"],
"required": [
"kind",
"id"
],
"properties": {
"kind": {
"type": "string",
"enum": ["message", "tool_call", "tool_result", "artifact_version", "source", "passage", "memory", "approval", "run", "url", "file", "build", "flux", "pod"]
"enum": [
"message",
"tool_call",
"tool_result",
"artifact_version",
"source",
"passage",
"memory",
"approval",
"run",
"url",
"file",
"build",
"flux",
"pod"
]
},
"id": {"type": "string", "minLength": 1, "maxLength": 200},
"uri": {"type": "string", "maxLength": 2000},
"hash": {"$ref": "#/$defs/sha256"}
"id": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"uri": {
"type": "string",
"maxLength": 2000
},
"hash": {
"$ref": "#/$defs/sha256"
}
}
},
"tags": {
"type": "array",
"maxItems": 32,
"uniqueItems": true,
"items": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,39}$"}
"items": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{0,39}$"
}
},
"tenant_slot": {
"type": "string",
"description": "Router-assigned tenant process slot.",
"pattern": "^slot-[0-9]{1,3}$"
},
"trust": {
"type": "string",
"description": "How the identity was asserted: router (browser via chat router), relay (Telegram relay key), worker (owner control plane).",
"enum": [
"router",
"relay",
"worker"
]
},
"identity": {
"type": "object",
"description": "Resolved caller identity. Every record path and authorization check derives from tenant_slot + subject.",
"additionalProperties": false,
"required": [
"tenant_slot",
"subject",
"surface",
"trust"
],
"properties": {
"tenant_slot": {
"$ref": "#/$defs/tenant_slot"
},
"subject": {
"$ref": "#/$defs/user_ref"
},
"surface": {
"$ref": "#/$defs/surface"
},
"trust": {
"$ref": "#/$defs/trust"
}
}
},
"revision": {
"type": "integer",
"description": "Optimistic concurrency counter. Mutations send If-Match: <revision>; a mismatch is 409.",
"minimum": 1
},
"idempotency_key": {
"type": "string",
"description": "Client-chosen key; a repeat with the same key returns the original record instead of creating a duplicate.",
"pattern": "^[A-Za-z0-9._:-]{8,120}$"
},
"contract_version": {
"type": "string",
"pattern": "^1\\.[0-9]+\\.[0-9]+$"
},
"audit_outcome": {
"type": "object",
"description": "Result of an authorization decision, written for every read and mutation.",
"additionalProperties": false,
"required": [
"at",
"identity",
"action",
"resource",
"outcome"
],
"properties": {
"at": {
"$ref": "#/$defs/timestamp"
},
"identity": {
"$ref": "#/$defs/identity"
},
"action": {
"type": "string",
"pattern": "^[a-z_]+\\.[a-z_]+$"
},
"resource": {
"type": "string",
"maxLength": 200
},
"outcome": {
"type": "string",
"enum": [
"allow",
"deny",
"not_found",
"conflict",
"flag_off"
]
},
"reason": {
"type": "string",
"maxLength": 200
}
}
}
}
}

View File

@ -5,38 +5,120 @@
"description": "One entry in the per-conversation activity stream (HUX-01). seq is strictly monotonic per conversation; consumers resume with after_seq. Every other HUX record that changes state emits exactly one event.",
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "seq", "ts", "conversation_id", "kind", "summary", "provenance", "sensitivity", "redaction"],
"required": [
"schema",
"id",
"seq",
"ts",
"conversation_id",
"kind",
"summary",
"provenance",
"sensitivity",
"redaction",
"turn",
"identity"
],
"properties": {
"schema": {"const": "hux.event.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"seq": {"type": "integer", "minimum": 0},
"ts": {"$ref": "common.schema.json#/$defs/timestamp"},
"conversation_id": {"$ref": "common.schema.json#/$defs/id"},
"run_id": {"type": "string", "maxLength": 120},
"parent_event_id": {"$ref": "common.schema.json#/$defs/id"},
"correlation_id": {"type": "string", "maxLength": 120},
"schema": {
"const": "hux.event.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"seq": {
"type": "integer",
"minimum": 0
},
"ts": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"conversation_id": {
"$ref": "common.schema.json#/$defs/id"
},
"run_id": {
"type": "string",
"maxLength": 120
},
"parent_event_id": {
"$ref": "common.schema.json#/$defs/id"
},
"correlation_id": {
"type": "string",
"maxLength": 120
},
"kind": {
"type": "string",
"enum": [
"message.user", "message.assistant",
"decision.route", "decision.plan",
"tool.call", "tool.result",
"approval.requested", "approval.resolved",
"memory.proposed", "memory.committed", "memory.forgotten",
"artifact.created", "artifact.version", "artifact.promoted",
"message.user",
"message.assistant",
"decision.route",
"decision.plan",
"tool.call",
"tool.result",
"approval.requested",
"approval.resolved",
"memory.proposed",
"memory.committed",
"memory.forgotten",
"artifact.created",
"artifact.version",
"artifact.promoted",
"citation.attached",
"mode.changed",
"run.started", "run.cancelled", "run.completed", "run.failed",
"run.started",
"run.cancelled",
"run.completed",
"run.failed",
"privacy.notice",
"suggestion.shown", "suggestion.dismissed",
"release.transition"
"suggestion.shown",
"suggestion.dismissed",
"release.transition",
"delegation.started",
"delegation.completed",
"delegation.failed",
"memory.suppressed",
"memory.retrieval_removed",
"budget.exhausted",
"side_effect.blocked",
"side_effect.released"
]
},
"summary": {"type": "string", "minLength": 1, "maxLength": 280},
"detail": {"type": "object", "description": "Kind-specific payload. Redacted according to redaction.level before leaving the tenant."},
"evidence": {"type": "array", "maxItems": 64, "items": {"$ref": "common.schema.json#/$defs/evidence_ref"}},
"provenance": {"$ref": "common.schema.json#/$defs/provenance"},
"sensitivity": {"$ref": "common.schema.json#/$defs/sensitivity"},
"redaction": {"$ref": "common.schema.json#/$defs/redaction"}
"summary": {
"type": "string",
"minLength": 1,
"maxLength": 280
},
"detail": {
"type": "object",
"description": "Kind-specific payload restricted to the allowlisted keys for that kind. Raw tool arguments, logs, credentials and tokens are never stored here; the redaction pipeline drops unknown keys and scrubs secret patterns before the event is written."
},
"evidence": {
"type": "array",
"maxItems": 64,
"items": {
"$ref": "common.schema.json#/$defs/evidence_ref"
}
},
"provenance": {
"$ref": "common.schema.json#/$defs/provenance"
},
"sensitivity": {
"$ref": "common.schema.json#/$defs/sensitivity"
},
"redaction": {
"$ref": "common.schema.json#/$defs/redaction"
},
"turn": {
"type": "integer",
"description": "User-visible turn number within the conversation; several events share a turn.",
"minimum": 0
},
"idempotency_key": {
"$ref": "common.schema.json#/$defs/idempotency_key"
},
"identity": {
"$ref": "common.schema.json#/$defs/identity"
}
}
}

View File

@ -1 +1,30 @@
{"schema":"hux.approval.v1","id":"apr_0001aaaa","run_id":"run_9f","conversation_id":"conv_0001abcd","capability":"write_files","request":{"summary":"Write supplier-comparison.md to the workspace","risk":"low","evidence":[{"kind":"tool_call","id":"call-7"}]},"status":"approved","decision":{"choice":"session","by":{"type":"user","id":"usr_0123456789abcdef"},"at":"2026-08-23T10:00:03Z"},"requested_at":"2026-08-23T10:00:02Z","expires_at":"2026-08-23T10:10:02Z"}
{
"schema": "hux.approval.v1",
"id": "apr_0001aaaa",
"run_id": "run_9f",
"conversation_id": "conv_0001abcd",
"capability": "write_files",
"request": {
"summary": "Write supplier-comparison.md to the workspace",
"risk": "low",
"evidence": [
{
"kind": "tool_call",
"id": "call-7"
}
],
"external": false
},
"status": "approved",
"decision": {
"choice": "session",
"by": {
"type": "user",
"id": "usr_0123456789abcdef"
},
"at": "2026-08-23T10:00:03Z"
},
"requested_at": "2026-08-23T10:00:02Z",
"expires_at": "2026-08-23T10:10:02Z",
"idempotency_key": "run_9f:approval:call-7"
}

View File

@ -1 +1,53 @@
{"schema":"hux.artifact.v1","id":"art_0001aaaa","owner":"usr_0123456789abcdef","conversation_id":"conv_0001abcd","project_id":"prj_0001aaaa","type":"markdown","title":"Supplier comparison","current_version":2,"versions":[{"version":1,"created_at":"2026-08-22T10:00:00Z","created_by":{"type":"assistant","id":"hermes"},"message_id":"msg-20","content_ref":{"hash":"sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881","bytes":2048,"mime":"text/markdown"}},{"version":2,"created_at":"2026-08-23T10:00:00Z","created_by":{"type":"user","id":"usr_0123456789abcdef"},"content_ref":{"hash":"sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881","bytes":2100,"mime":"text/markdown"},"diff_from":1,"note":"added delivery lead times"}],"promotion":{"project_id":"prj_0001aaaa","version":2,"at":"2026-08-23T10:00:05Z"},"sensitivity":"personal","created_at":"2026-08-22T10:00:00Z","updated_at":"2026-08-23T10:00:05Z"}
{
"schema": "hux.artifact.v1",
"id": "art_0001aaaa",
"owner": "usr_0123456789abcdef",
"conversation_id": "conv_0001abcd",
"project_id": "prj_0001aaaa",
"type": "markdown",
"title": "Supplier comparison",
"current_version": 2,
"versions": [
{
"version": 1,
"created_at": "2026-08-22T10:00:00Z",
"created_by": {
"type": "assistant",
"id": "hermes"
},
"message_id": "msg-20",
"content_ref": {
"hash": "sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881",
"bytes": 2048,
"mime": "text/markdown"
}
},
{
"version": 2,
"created_at": "2026-08-23T10:00:00Z",
"created_by": {
"type": "user",
"id": "usr_0123456789abcdef"
},
"content_ref": {
"hash": "sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881",
"bytes": 2100,
"mime": "text/markdown"
},
"diff_from": 1,
"note": "added delivery lead times"
}
],
"promotion": {
"project_id": "prj_0001aaaa",
"version": 2,
"at": "2026-08-23T10:00:05Z"
},
"sensitivity": "personal",
"created_at": "2026-08-22T10:00:00Z",
"updated_at": "2026-08-23T10:00:05Z",
"revision": 2,
"access": {
"mode": "owner"
}
}

View File

@ -0,0 +1,20 @@
{
"schema": "hux.budget_state.v1",
"run_id": "run_9f",
"spent": {
"tokens": 120000,
"tool_calls": 12,
"wall_clock_seconds": 300,
"delegations": 1,
"spend_units": 10,
"subagents": 1
},
"limits": {
"tokens_per_run": 200000,
"tool_calls_per_run": 40,
"wall_clock_seconds": 900,
"spend_units": 50,
"subagents_per_run": 2
},
"exhausted": []
}

View File

@ -1 +1,24 @@
{"schema":"hux.cancel_receipt.v1","id":"rcpt_0001aaaa","run_id":"run_9f","requested_by":{"type":"user","id":"usr_0123456789abcdef"},"requested_at":"2026-08-23T10:00:04Z","acknowledged_at":"2026-08-23T10:00:04Z","completed_at":"2026-08-23T10:00:05Z","outcome":"cancelled","side_effects":[{"description":"Partial file supplier-comparison.md left in workspace","reverted":true,"evidence":{"kind":"file","id":"supplier-comparison.md"}}]}
{
"schema": "hux.cancel_receipt.v1",
"id": "rcpt_0001aaaa",
"run_id": "run_9f",
"requested_by": {
"type": "user",
"id": "usr_0123456789abcdef"
},
"requested_at": "2026-08-23T10:00:04Z",
"acknowledged_at": "2026-08-23T10:00:04Z",
"completed_at": "2026-08-23T10:00:05Z",
"outcome": "cancelled",
"side_effects": [
{
"description": "Partial file supplier-comparison.md left in workspace",
"reverted": true,
"evidence": {
"kind": "file",
"id": "supplier-comparison.md"
}
}
],
"conversation_id": "conv_0001abcd"
}

View File

@ -0,0 +1,33 @@
{
"schema": "hux.capabilities.v1",
"contract_version": "1.0.0",
"identity": {
"tenant_slot": "slot-3",
"subject": "usr_0123456789abcdef",
"surface": "chat",
"trust": "router"
},
"cards": [
{
"card": "HUX-11",
"flag": "hux.foundation",
"enabled": true,
"routes": [
"/hux/v1/capabilities"
]
},
{
"card": "HUX-01",
"flag": "hux.activity_timeline",
"enabled": false,
"routes": [
"/hux/v1/conversations/{id}/events",
"/hux/v1/conversations/{id}/events/stream"
]
}
],
"server": {
"commit": "d3cbeb06d3cbeb06d3cbeb06d3cbeb06d3cbeb06",
"image_digest": "sha256:4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd"
}
}

View File

@ -1 +1,11 @@
{"schema":"hux.citation.v1","id":"cit_0001aaaa","message_id":"msg-44","claim":"Base cabinets from Example Co take three to four weeks.","passage_ids":["psg_0001aaaa"],"support":"supports"}
{
"schema": "hux.citation.v1",
"id": "cit_0001aaaa",
"message_id": "msg-44",
"claim": "Base cabinets from Example Co take three to four weeks.",
"passage_ids": [
"psg_0001aaaa"
],
"support": "supports",
"dedupe_key": "sha256:abababababababababababababababababababababababababababababababab"
}

View File

@ -1 +1,24 @@
{"schema":"hux.conversation.v1","id":"conv_0001abcd","owner":"usr_0123456789abcdef","project_id":"prj_0001aaaa","title":"Compare cabinet suppliers","tags":["suppliers"],"pinned":false,"archived":false,"mode":"research","branch":{"parent_conversation_id":"conv_0000root","branch_point_message_id":"msg-12"},"artifact_ids":["art_0001aaaa"],"last_message_at":"2026-08-23T10:00:01Z","created_at":"2026-08-22T09:00:00Z","updated_at":"2026-08-23T10:00:01Z"}
{
"schema": "hux.conversation.v1",
"id": "conv_0001abcd",
"owner": "usr_0123456789abcdef",
"project_id": "prj_0001aaaa",
"title": "Compare cabinet suppliers",
"tags": [
"suppliers"
],
"pinned": false,
"archived": false,
"mode": "research",
"branch": {
"parent_conversation_id": "conv_0000root",
"branch_point_message_id": "msg-12"
},
"artifact_ids": [
"art_0001aaaa"
],
"last_message_at": "2026-08-23T10:00:01Z",
"created_at": "2026-08-22T09:00:00Z",
"updated_at": "2026-08-23T10:00:01Z",
"revision": 3
}

View File

@ -0,0 +1,9 @@
{
"schema": "hux.error.v1",
"status": 409,
"code": "conflict",
"message": "revision 2 does not match current revision 3",
"details": [
"If-Match: 2"
]
}

View File

@ -1 +1,52 @@
{"schema":"hux.event.v1","id":"evt_0001aaaa","seq":17,"ts":"2026-08-23T10:00:01Z","conversation_id":"conv_0001abcd","run_id":"run_9f","kind":"decision.route","summary":"Routed to a high-effort Claude target for a multi-step refactor","detail":{"candidates":3},"evidence":[{"kind":"run","id":"run_9f"}],"provenance":{"surface":"chat","actor":{"type":"assistant","id":"hermes"},"recorded_at":"2026-08-23T10:00:00Z","session_id":"sess-a1","conversation_id":"conv_0001abcd","run_id":"run_9f","route":{"requested":"thoughtful","resolved_target":"claude_sonnet_high","provider":"claude","effort":"high"},"build":{"commit":"d3cbeb06d3cbeb06d3cbeb06d3cbeb06d3cbeb06","image_digest":"sha256:4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd"}},"sensitivity":"personal","redaction":{"level":"none"}}
{
"schema": "hux.event.v1",
"id": "evt_0001aaaa",
"seq": 17,
"ts": "2026-08-23T10:00:01Z",
"conversation_id": "conv_0001abcd",
"run_id": "run_9f",
"kind": "decision.route",
"summary": "Routed to a high-effort Claude target for a multi-step refactor",
"detail": {
"candidates": 3
},
"evidence": [
{
"kind": "run",
"id": "run_9f"
}
],
"provenance": {
"surface": "chat",
"actor": {
"type": "assistant",
"id": "hermes"
},
"recorded_at": "2026-08-23T10:00:00Z",
"session_id": "sess-a1",
"conversation_id": "conv_0001abcd",
"run_id": "run_9f",
"route": {
"requested": "thoughtful",
"resolved_target": "claude_sonnet_high",
"provider": "claude",
"effort": "high"
},
"build": {
"commit": "d3cbeb06d3cbeb06d3cbeb06d3cbeb06d3cbeb06",
"image_digest": "sha256:4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd"
}
},
"sensitivity": "personal",
"redaction": {
"level": "none"
},
"turn": 5,
"identity": {
"tenant_slot": "slot-3",
"subject": "usr_0123456789abcdef",
"surface": "chat",
"trust": "router"
},
"idempotency_key": "run_9f:decision:17"
}

View File

@ -0,0 +1,8 @@
{
"schema": "hux.manifest.v1",
"contract_version": "1.0.0",
"data_layout_version": 1,
"min_reader_contract_version": "1.0.0",
"created_at": "2026-08-24T00:00:00Z",
"updated_at": "2026-08-24T00:00:00Z"
}

View File

@ -1 +1,65 @@
{"schema":"hux.memory.v1","id":"mem_0001aaaa","owner":"usr_0123456789abcdef","scope":{"level":"project","scope_id":"prj_0001aaaa"},"kind":"preference","content":"Prefers terse answers with code first.","status":"proposed","approval_mode":"ask","sensitivity":"personal","topic":"general","ttl":{"policy":"decay","decay_days":180},"source":{"kind":"message","id":"msg-42"},"provenance":{"surface":"chat","actor":{"type":"assistant","id":"hermes"},"recorded_at":"2026-08-23T10:00:00Z","session_id":"sess-a1","conversation_id":"conv_0001abcd","run_id":"run_9f","route":{"requested":"thoughtful","resolved_target":"claude_sonnet_high","provider":"claude","effort":"high"},"build":{"commit":"d3cbeb06d3cbeb06d3cbeb06d3cbeb06d3cbeb06","image_digest":"sha256:4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd"}},"created_at":"2026-08-23T10:00:00Z","updated_at":"2026-08-23T10:00:00Z","audit":[{"at":"2026-08-23T10:00:00Z","action":"proposed","actor":{"type":"assistant","id":"hermes"}}]}
{
"schema": "hux.memory.v1",
"id": "mem_0001aaaa",
"owner": "usr_0123456789abcdef",
"scope": {
"level": "project",
"scope_id": "prj_0001aaaa"
},
"kind": "preference",
"content": "Prefers terse answers with code first.",
"status": "proposed",
"approval_mode": "ask",
"sensitivity": "personal",
"topic": "general",
"ttl": {
"policy": "decay",
"decay_days": 180
},
"source": {
"kind": "message",
"id": "msg-42"
},
"provenance": {
"surface": "chat",
"actor": {
"type": "assistant",
"id": "hermes"
},
"recorded_at": "2026-08-23T10:00:00Z",
"session_id": "sess-a1",
"conversation_id": "conv_0001abcd",
"run_id": "run_9f",
"route": {
"requested": "thoughtful",
"resolved_target": "claude_sonnet_high",
"provider": "claude",
"effort": "high"
},
"build": {
"commit": "d3cbeb06d3cbeb06d3cbeb06d3cbeb06d3cbeb06",
"image_digest": "sha256:4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd"
}
},
"created_at": "2026-08-23T10:00:00Z",
"updated_at": "2026-08-23T10:00:00Z",
"audit": [
{
"at": "2026-08-23T10:00:00Z",
"action": "proposed",
"actor": {
"type": "assistant",
"id": "hermes"
}
}
],
"reason": "You asked for code-first answers twice in this project.",
"retrievable": false,
"revision": 1,
"identity": {
"tenant_slot": "slot-3",
"subject": "usr_0123456789abcdef",
"surface": "chat",
"trust": "router"
}
}

View File

@ -1 +1,31 @@
{"schema":"hux.research_notebook.v1","id":"nb_0001aaaa","conversation_id":"conv_0001abcd","question":"Which supplier delivers fastest under budget?","status":"open","source_ids":["src_0001aaaa"],"passage_ids":["psg_0001aaaa"],"citation_ids":["cit_0001aaaa"],"assumptions":["Budget cap is unchanged at 12k"],"unresolved_questions":["Does the lead time include installation?"],"updated_at":"2026-08-23T10:00:01Z"}
{
"schema": "hux.research_notebook.v1",
"id": "nb_0001aaaa",
"conversation_id": "conv_0001abcd",
"question": "Which supplier delivers fastest under budget?",
"status": "open",
"source_ids": [
"src_0001aaaa"
],
"passage_ids": [
"psg_0001aaaa"
],
"citation_ids": [
"cit_0001aaaa"
],
"assumptions": [
"Budget cap is unchanged at 12k"
],
"unresolved_questions": [
"Does the lead time include installation?"
],
"updated_at": "2026-08-23T10:00:01Z",
"notes": [
{
"at": "2026-08-23T10:00:00Z",
"text": "Lead times exclude installation per supplier FAQ.",
"source_id": "src_0001aaaa"
}
],
"revision": 4
}

View File

@ -1 +1,13 @@
{"schema":"hux.passage.v1","id":"psg_0001aaaa","source_id":"src_0001aaaa","text":"Standard base cabinets ship in 3-4 weeks.","hash":"sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881","locator":{"selector":"#lead-times","char_start":120,"char_end":160}}
{
"schema": "hux.passage.v1",
"id": "psg_0001aaaa",
"source_id": "src_0001aaaa",
"text": "Standard base cabinets ship in 3-4 weeks.",
"hash": "sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881",
"locator": {
"selector": "#lead-times",
"char_start": 120,
"char_end": 160
},
"dedupe_key": "sha256:abababababababababababababababababababababababababababababababab"
}

View File

@ -1 +1,56 @@
{"schema":"hux.policy.v1","id":"pol_0001aaaa","owner":"usr_0123456789abcdef","scope":{"level":"conversation","scope_id":"conv_0001abcd"},"autonomy":"safe","grants":[{"capability":"network","decision":"ask","expires_at":"2026-08-24T10:00:00Z","granted_by":{"type":"user","id":"usr_0123456789abcdef"}}],"budgets":{"tokens_per_run":200000,"tool_calls_per_run":40,"wall_clock_seconds":900},"provenance":{"surface":"chat","actor":{"type":"assistant","id":"hermes"},"recorded_at":"2026-08-23T10:00:00Z","session_id":"sess-a1","conversation_id":"conv_0001abcd","run_id":"run_9f","route":{"requested":"thoughtful","resolved_target":"claude_sonnet_high","provider":"claude","effort":"high"},"build":{"commit":"d3cbeb06d3cbeb06d3cbeb06d3cbeb06d3cbeb06","image_digest":"sha256:4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd"}},"updated_at":"2026-08-23T10:00:00Z"}
{
"schema": "hux.policy.v1",
"id": "pol_0001aaaa",
"owner": "usr_0123456789abcdef",
"scope": {
"level": "conversation",
"scope_id": "conv_0001abcd"
},
"autonomy": "safe",
"grants": [
{
"capability": "network",
"decision": "ask",
"expires_at": "2026-08-24T10:00:00Z",
"granted_by": {
"type": "user",
"id": "usr_0123456789abcdef"
}
}
],
"budgets": {
"tokens_per_run": 200000,
"tool_calls_per_run": 40,
"wall_clock_seconds": 900,
"spend_units": 50,
"subagents_per_run": 2,
"scope": {
"conversations": [
"conv_0001abcd"
]
}
},
"provenance": {
"surface": "chat",
"actor": {
"type": "assistant",
"id": "hermes"
},
"recorded_at": "2026-08-23T10:00:00Z",
"session_id": "sess-a1",
"conversation_id": "conv_0001abcd",
"run_id": "run_9f",
"route": {
"requested": "thoughtful",
"resolved_target": "claude_sonnet_high",
"provider": "claude",
"effort": "high"
},
"build": {
"commit": "d3cbeb06d3cbeb06d3cbeb06d3cbeb06d3cbeb06",
"image_digest": "sha256:4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd"
}
},
"updated_at": "2026-08-23T10:00:00Z",
"revision": 1
}

View File

@ -1 +1,17 @@
{"schema":"hux.project.v1","id":"prj_0001aaaa","owner":"usr_0123456789abcdef","name":"Kitchen renovation","description":"Quotes, plans and supplier research.","tags":["home","2026"],"pinned":true,"archived":false,"default_mode":"research","created_at":"2026-08-20T09:00:00Z","updated_at":"2026-08-23T10:00:00Z"}
{
"schema": "hux.project.v1",
"id": "prj_0001aaaa",
"owner": "usr_0123456789abcdef",
"name": "Kitchen renovation",
"description": "Quotes, plans and supplier research.",
"tags": [
"home",
"2026"
],
"pinned": true,
"archived": false,
"default_mode": "research",
"created_at": "2026-08-20T09:00:00Z",
"updated_at": "2026-08-23T10:00:00Z",
"revision": 3
}

View File

@ -1 +1,33 @@
{"schema":"hux.source.v1","id":"src_0001aaaa","kind":"web","uri":"https://example.com/cabinets","title":"Cabinet pricing 2026","publisher":"Example Co","retrieved_at":"2026-08-23T09:59:00Z","classification":"primary","content_hash":"sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881","provenance":{"surface":"chat","actor":{"type":"assistant","id":"hermes"},"recorded_at":"2026-08-23T10:00:00Z","session_id":"sess-a1","conversation_id":"conv_0001abcd","run_id":"run_9f","route":{"requested":"thoughtful","resolved_target":"claude_sonnet_high","provider":"claude","effort":"high"},"build":{"commit":"d3cbeb06d3cbeb06d3cbeb06d3cbeb06d3cbeb06","image_digest":"sha256:4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd"}}}
{
"schema": "hux.source.v1",
"id": "src_0001aaaa",
"kind": "web",
"uri": "https://example.com/cabinets",
"title": "Cabinet pricing 2026",
"publisher": "Example Co",
"retrieved_at": "2026-08-23T09:59:00Z",
"classification": "primary",
"content_hash": "sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881",
"provenance": {
"surface": "chat",
"actor": {
"type": "assistant",
"id": "hermes"
},
"recorded_at": "2026-08-23T10:00:00Z",
"session_id": "sess-a1",
"conversation_id": "conv_0001abcd",
"run_id": "run_9f",
"route": {
"requested": "thoughtful",
"resolved_target": "claude_sonnet_high",
"provider": "claude",
"effort": "high"
},
"build": {
"commit": "d3cbeb06d3cbeb06d3cbeb06d3cbeb06d3cbeb06",
"image_digest": "sha256:4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd4a385fbd"
}
},
"dedupe_key": "sha256:abababababababababababababababababababababababababababababababab"
}

View File

@ -2,17 +2,202 @@
"schema": "hux.flags.v1",
"env_var": "HUX_FLAGS",
"cards": [
{"card": "HUX-01", "flag": "hux.activity_timeline", "title": "Activity timeline", "wave": "A", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["event.schema.json"], "depends_on": ["HUX-11"], "default": false, "rollback": "disable flag; events keep accumulating on the tenant PVC and are not user-visible"},
{"card": "HUX-02", "flag": "hux.memory_control", "title": "Memory control", "wave": "A", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["memory.schema.json"], "depends_on": ["HUX-11", "HUX-10"], "default": false, "rollback": "disable flag; ledger stays read-only, upstream memory tool unchanged"},
{"card": "HUX-03", "flag": "hux.projects", "title": "Projects and chats", "wave": "B", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["project.schema.json"], "depends_on": ["HUX-11"], "default": false, "rollback": "disable flag; upstream project list remains authoritative"},
{"card": "HUX-04", "flag": "hux.artifacts", "title": "Artifact workspace", "wave": "B", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["artifact.schema.json"], "depends_on": ["HUX-11", "HUX-03"], "default": false, "rollback": "disable flag; artifacts remain downloadable files in the tenant workspace"},
{"card": "HUX-05", "flag": "hux.autonomy", "title": "Autonomy controls", "wave": "A", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["permission.schema.json"], "depends_on": ["HUX-11", "HUX-01"], "default": false, "rollback": "disable flag; gateway approval mode reverts to the tenant default"},
{"card": "HUX-06", "flag": "hux.friendly_modes", "title": "Friendly modes", "wave": "B", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["mode.schema.json"], "depends_on": ["HUX-11"], "default": false, "rollback": "disable flag; routing-priority chips remain"},
{"card": "HUX-07", "flag": "hux.multimodal", "title": "Multimodal", "wave": "C", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["artifact.schema.json", "event.schema.json"], "depends_on": ["HUX-04", "HUX-05"], "default": false, "rollback": "disable flag; upstream upload path remains"},
{"card": "HUX-08", "flag": "hux.research", "title": "Research and citations", "wave": "B", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["citation.schema.json"], "depends_on": ["HUX-11", "HUX-06"], "default": false, "rollback": "disable flag; prose citations remain"},
{"card": "HUX-09", "flag": "hux.onboarding", "title": "Onboarding", "wave": "C", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["suggestion.schema.json"], "depends_on": ["HUX-03", "HUX-06"], "default": false, "rollback": "disable flag; no suggestions shown"},
{"card": "HUX-10", "flag": "hux.privacy", "title": "Privacy behaviour", "wave": "A", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["privacy.schema.json"], "depends_on": ["HUX-11"], "default": false, "rollback": "disable flag; tenant isolation remains the privacy model"},
{"card": "HUX-11", "flag": "hux.foundation", "title": "Shared foundation", "wave": "A", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["common.schema.json"], "depends_on": [], "default": false, "rollback": "disable flag; /hux/v1 returns 404 from the router"},
{"card": "HUX-12", "flag": "hux.release_followthrough", "title": "Deployment follow-through", "wave": "A", "backend_owner": "claude", "frontend_owner": "codex", "contracts": ["release.schema.json"], "depends_on": [], "default": false, "rollback": "disable flag; release lane evidence archive remains the source of truth"}
]
{
"card": "HUX-01",
"flag": "hux.activity_timeline",
"title": "Activity timeline",
"wave": "A",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"event.schema.json"
],
"depends_on": [
"HUX-11"
],
"default": false,
"rollback": "disable flag; events keep accumulating on the tenant PVC and are not user-visible"
},
{
"card": "HUX-02",
"flag": "hux.memory_control",
"title": "Memory control",
"wave": "A",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"memory.schema.json"
],
"depends_on": [
"HUX-11",
"HUX-10"
],
"default": false,
"rollback": "disable flag; ledger stays read-only, upstream memory tool unchanged"
},
{
"card": "HUX-03",
"flag": "hux.projects",
"title": "Projects and chats",
"wave": "B",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"project.schema.json"
],
"depends_on": [
"HUX-11"
],
"default": false,
"rollback": "disable flag; upstream project list remains authoritative"
},
{
"card": "HUX-04",
"flag": "hux.artifacts",
"title": "Artifact workspace",
"wave": "B",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"artifact.schema.json"
],
"depends_on": [
"HUX-11",
"HUX-03"
],
"default": false,
"rollback": "disable flag; artifacts remain downloadable files in the tenant workspace"
},
{
"card": "HUX-05",
"flag": "hux.autonomy",
"title": "Autonomy controls",
"wave": "A",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"permission.schema.json"
],
"depends_on": [
"HUX-11",
"HUX-01"
],
"default": false,
"rollback": "disable flag; gateway approval mode reverts to the tenant default"
},
{
"card": "HUX-06",
"flag": "hux.friendly_modes",
"title": "Friendly modes",
"wave": "B",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"mode.schema.json"
],
"depends_on": [
"HUX-11"
],
"default": false,
"rollback": "disable flag; routing-priority chips remain"
},
{
"card": "HUX-07",
"flag": "hux.multimodal",
"title": "Multimodal",
"wave": "C",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"artifact.schema.json",
"event.schema.json"
],
"depends_on": [
"HUX-04",
"HUX-05"
],
"default": false,
"rollback": "disable flag; upstream upload path remains"
},
{
"card": "HUX-08",
"flag": "hux.research",
"title": "Research and citations",
"wave": "B",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"citation.schema.json"
],
"depends_on": [
"HUX-11",
"HUX-06"
],
"default": false,
"rollback": "disable flag; prose citations remain"
},
{
"card": "HUX-09",
"flag": "hux.onboarding",
"title": "Onboarding",
"wave": "C",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"suggestion.schema.json"
],
"depends_on": [
"HUX-03",
"HUX-06"
],
"default": false,
"rollback": "disable flag; no suggestions shown"
},
{
"card": "HUX-10",
"flag": "hux.privacy",
"title": "Privacy behaviour",
"wave": "A",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"privacy.schema.json"
],
"depends_on": [
"HUX-11"
],
"default": false,
"rollback": "disable flag; tenant isolation remains the privacy model"
},
{
"card": "HUX-11",
"flag": "hux.foundation",
"title": "Shared foundation",
"wave": "A",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"common.schema.json",
"identity.schema.json"
],
"depends_on": [],
"default": false,
"rollback": "disable flag; /hux/v1 returns 404 from the router"
},
{
"card": "HUX-12",
"flag": "hux.release_followthrough",
"title": "Deployment follow-through",
"wave": "A",
"backend_owner": "claude",
"frontend_owner": "codex",
"contracts": [
"release.schema.json"
],
"depends_on": [],
"default": false,
"rollback": "disable flag; release lane evidence archive remains the source of truth"
}
],
"contract_version": "1.0.0"
}

View File

@ -0,0 +1,157 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://hermes.bstein.dev/contracts/hux/v1/identity.schema.json",
"title": "HUX identity, capability negotiation and data manifest",
"description": "HUX-11 foundation records. capabilities is the first call every client makes: it learns the contract version, which cards are on for this tenant (a card is on only when its whole dependency chain is on), and the server build. manifest is stored on the tenant PVC and tells a rolled-back reader whether it can read the data.",
"$defs": {
"capabilities": {
"type": "object",
"additionalProperties": false,
"required": [
"schema",
"contract_version",
"identity",
"cards",
"server"
],
"properties": {
"schema": {
"const": "hux.capabilities.v1"
},
"contract_version": {
"$ref": "common.schema.json#/$defs/contract_version"
},
"identity": {
"$ref": "common.schema.json#/$defs/identity"
},
"cards": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"card",
"flag",
"enabled",
"routes"
],
"properties": {
"card": {
"type": "string",
"pattern": "^HUX-[0-9]{2}$"
},
"flag": {
"type": "string",
"pattern": "^hux\\.[a-z_]+$"
},
"enabled": {
"type": "boolean"
},
"routes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^/hux/v1/"
}
}
}
}
},
"server": {
"$ref": "common.schema.json#/$defs/build"
}
}
},
"manifest": {
"type": "object",
"additionalProperties": false,
"required": [
"schema",
"contract_version",
"data_layout_version",
"min_reader_contract_version",
"created_at",
"updated_at"
],
"properties": {
"schema": {
"const": "hux.manifest.v1"
},
"contract_version": {
"$ref": "common.schema.json#/$defs/contract_version"
},
"data_layout_version": {
"type": "integer",
"minimum": 1
},
"min_reader_contract_version": {
"$ref": "common.schema.json#/$defs/contract_version"
},
"created_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"updated_at": {
"$ref": "common.schema.json#/$defs/timestamp"
}
}
},
"error": {
"type": "object",
"additionalProperties": false,
"required": [
"schema",
"status",
"code",
"message"
],
"properties": {
"schema": {
"const": "hux.error.v1"
},
"status": {
"type": "integer",
"minimum": 400,
"maximum": 599
},
"code": {
"type": "string",
"enum": [
"unauthorized",
"forbidden",
"not_found",
"conflict",
"flag_off",
"invalid",
"too_large",
"rate_limited",
"approval_required",
"budget_exhausted"
]
},
"message": {
"type": "string",
"maxLength": 280
},
"details": {
"type": "array",
"maxItems": 32,
"items": {
"type": "string",
"maxLength": 280
}
}
}
}
},
"oneOf": [
{
"$ref": "#/$defs/capabilities"
},
{
"$ref": "#/$defs/manifest"
},
{
"$ref": "#/$defs/error"
}
]
}

View File

@ -5,54 +5,201 @@
"description": "A single remembered item (HUX-02). Memory is a ledger: entries are appended, approved, expired or forgotten, never edited in place. Forgotten entries keep their audit trail but drop content.",
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "owner", "scope", "kind", "content", "status", "approval_mode", "sensitivity", "ttl", "source", "provenance", "created_at", "updated_at", "audit"],
"required": [
"schema",
"id",
"owner",
"scope",
"kind",
"content",
"status",
"approval_mode",
"sensitivity",
"ttl",
"source",
"provenance",
"created_at",
"updated_at",
"audit",
"reason",
"retrievable",
"revision"
],
"properties": {
"schema": {"const": "hux.memory.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"owner": {"$ref": "common.schema.json#/$defs/user_ref"},
"schema": {
"const": "hux.memory.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"owner": {
"$ref": "common.schema.json#/$defs/user_ref"
},
"scope": {
"type": "object",
"additionalProperties": false,
"required": ["level"],
"required": [
"level"
],
"properties": {
"level": {"type": "string", "enum": ["global", "project", "conversation"]},
"scope_id": {"$ref": "common.schema.json#/$defs/id"}
"level": {
"type": "string",
"enum": [
"global",
"project",
"conversation"
]
},
"scope_id": {
"$ref": "common.schema.json#/$defs/id"
}
}
},
"kind": {"type": "string", "enum": ["preference", "fact", "instruction", "context"]},
"content": {"type": "string", "maxLength": 2000},
"status": {"type": "string", "enum": ["proposed", "active", "rejected", "expired", "forgotten"]},
"approval_mode": {"type": "string", "enum": ["automatic", "ask"]},
"sensitivity": {"$ref": "common.schema.json#/$defs/sensitivity"},
"topic": {"type": "string", "enum": ["general", "health", "finance", "legal", "relationships", "credentials", "minors", "location", "biometric"]},
"kind": {
"type": "string",
"enum": [
"preference",
"fact",
"instruction",
"context"
]
},
"content": {
"type": "string",
"maxLength": 2000
},
"status": {
"type": "string",
"enum": [
"proposed",
"active",
"rejected",
"expired",
"forgotten",
"no_store"
]
},
"approval_mode": {
"type": "string",
"enum": [
"automatic",
"ask",
"no_store"
]
},
"sensitivity": {
"$ref": "common.schema.json#/$defs/sensitivity"
},
"topic": {
"type": "string",
"enum": [
"general",
"health",
"finance",
"legal",
"relationships",
"credentials",
"minors",
"location",
"biometric"
]
},
"ttl": {
"type": "object",
"additionalProperties": false,
"required": ["policy"],
"required": [
"policy"
],
"properties": {
"policy": {"type": "string", "enum": ["never", "expires_at", "decay"]},
"expires_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"decay_days": {"type": "integer", "minimum": 1, "maximum": 3650}
"policy": {
"type": "string",
"enum": [
"never",
"expires_at",
"decay"
]
},
"expires_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"decay_days": {
"type": "integer",
"minimum": 1,
"maximum": 3650
}
}
},
"source": {"$ref": "common.schema.json#/$defs/evidence_ref"},
"provenance": {"$ref": "common.schema.json#/$defs/provenance"},
"created_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"updated_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"source": {
"$ref": "common.schema.json#/$defs/evidence_ref"
},
"provenance": {
"$ref": "common.schema.json#/$defs/provenance"
},
"created_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"updated_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"audit": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["at", "action", "actor"],
"required": [
"at",
"action",
"actor"
],
"properties": {
"at": {"$ref": "common.schema.json#/$defs/timestamp"},
"action": {"type": "string", "enum": ["proposed", "approved", "rejected", "expired", "forgotten", "exported"]},
"actor": {"$ref": "common.schema.json#/$defs/actor"},
"note": {"type": "string", "maxLength": 200}
"at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"action": {
"type": "string",
"enum": [
"proposed",
"approved",
"rejected",
"expired",
"forgotten",
"exported",
"no_store",
"superseded",
"retrieval_removed",
"edited"
]
},
"actor": {
"$ref": "common.schema.json#/$defs/actor"
},
"note": {
"type": "string",
"maxLength": 200
}
}
}
},
"reason": {
"type": "string",
"description": "Why the assistant proposed remembering this, in the user's terms.",
"minLength": 1,
"maxLength": 280
},
"supersedes": {
"$ref": "common.schema.json#/$defs/id",
"description": "An edit is a new entry that supersedes the old one; the old entry moves to forgotten."
},
"retrievable": {
"type": "boolean",
"description": "False removes the entry from retrieval without deleting audit. Forgotten, rejected, expired and no_store entries are never retrievable."
},
"revision": {
"$ref": "common.schema.json#/$defs/revision"
},
"identity": {
"$ref": "common.schema.json#/$defs/identity"
}
}
}

View File

@ -6,39 +6,105 @@
"$defs": {
"capability": {
"type": "string",
"enum": ["read_files", "write_files", "shell", "network", "web_search", "send_message", "memory_write", "artifact_write", "spend_tokens", "delegate", "deploy"]
"enum": [
"read_files",
"write_files",
"shell",
"network",
"web_search",
"send_message",
"memory_write",
"artifact_write",
"spend_tokens",
"delegate",
"deploy",
"external_side_effect"
]
},
"decision": {
"type": "string",
"enum": [
"allow",
"ask",
"deny"
]
},
"decision": {"type": "string", "enum": ["allow", "ask", "deny"]},
"policy": {
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "owner", "scope", "autonomy", "grants", "budgets", "provenance", "updated_at"],
"required": [
"schema",
"id",
"owner",
"scope",
"autonomy",
"grants",
"budgets",
"provenance",
"updated_at",
"revision"
],
"properties": {
"schema": {"const": "hux.policy.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"owner": {"$ref": "common.schema.json#/$defs/user_ref"},
"schema": {
"const": "hux.policy.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"owner": {
"$ref": "common.schema.json#/$defs/user_ref"
},
"scope": {
"type": "object",
"additionalProperties": false,
"required": ["level"],
"required": [
"level"
],
"properties": {
"level": {"type": "string", "enum": ["global", "project", "conversation"]},
"scope_id": {"$ref": "common.schema.json#/$defs/id"}
"level": {
"type": "string",
"enum": [
"global",
"project",
"conversation"
]
},
"scope_id": {
"$ref": "common.schema.json#/$defs/id"
}
}
},
"autonomy": {"type": "string", "enum": ["ask_first", "safe", "autonomous"]},
"autonomy": {
"type": "string",
"enum": [
"ask_first",
"safe",
"autonomous"
]
},
"grants": {
"type": "array",
"maxItems": 64,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["capability", "decision"],
"required": [
"capability",
"decision"
],
"properties": {
"capability": {"$ref": "#/$defs/capability"},
"decision": {"$ref": "#/$defs/decision"},
"expires_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"granted_by": {"$ref": "common.schema.json#/$defs/actor"}
"capability": {
"$ref": "#/$defs/capability"
},
"decision": {
"$ref": "#/$defs/decision"
},
"expires_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"granted_by": {
"$ref": "common.schema.json#/$defs/actor"
}
}
}
},
@ -46,85 +112,337 @@
"type": "object",
"additionalProperties": false,
"properties": {
"tokens_per_run": {"type": "integer", "minimum": 0},
"tool_calls_per_run": {"type": "integer", "minimum": 0},
"wall_clock_seconds": {"type": "integer", "minimum": 0},
"delegations_per_run": {"type": "integer", "minimum": 0}
"tokens_per_run": {
"type": "integer",
"minimum": 0
},
"tool_calls_per_run": {
"type": "integer",
"minimum": 0
},
"wall_clock_seconds": {
"type": "integer",
"minimum": 0
},
"delegations_per_run": {
"type": "integer",
"minimum": 0
},
"spend_units": {
"type": "integer",
"minimum": 0
},
"subagents_per_run": {
"type": "integer",
"minimum": 0
},
"scope": {
"type": "object",
"additionalProperties": false,
"properties": {
"conversations": {
"type": "array",
"maxItems": 64,
"items": {
"$ref": "common.schema.json#/$defs/id"
}
},
"paths": {
"type": "array",
"maxItems": 64,
"items": {
"type": "string",
"maxLength": 300
}
}
}
}
}
},
"provenance": {"$ref": "common.schema.json#/$defs/provenance"},
"updated_at": {"$ref": "common.schema.json#/$defs/timestamp"}
"provenance": {
"$ref": "common.schema.json#/$defs/provenance"
},
"updated_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"revision": {
"$ref": "common.schema.json#/$defs/revision"
}
}
},
"approval": {
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "run_id", "conversation_id", "capability", "request", "status", "requested_at", "expires_at"],
"required": [
"schema",
"id",
"run_id",
"conversation_id",
"capability",
"request",
"status",
"requested_at",
"expires_at"
],
"properties": {
"schema": {"const": "hux.approval.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"run_id": {"type": "string", "minLength": 1, "maxLength": 120},
"conversation_id": {"$ref": "common.schema.json#/$defs/id"},
"capability": {"$ref": "#/$defs/capability"},
"schema": {
"const": "hux.approval.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"run_id": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"conversation_id": {
"$ref": "common.schema.json#/$defs/id"
},
"capability": {
"$ref": "#/$defs/capability"
},
"request": {
"type": "object",
"additionalProperties": false,
"required": ["summary", "risk"],
"required": [
"summary",
"risk",
"external"
],
"properties": {
"summary": {"type": "string", "minLength": 1, "maxLength": 280},
"detail": {"type": "string", "maxLength": 4000},
"risk": {"type": "string", "enum": ["low", "medium", "high"]},
"evidence": {"type": "array", "maxItems": 16, "items": {"$ref": "common.schema.json#/$defs/evidence_ref"}}
"summary": {
"type": "string",
"minLength": 1,
"maxLength": 280
},
"detail": {
"type": "string",
"maxLength": 4000
},
"risk": {
"type": "string",
"enum": [
"low",
"medium",
"high"
]
},
"evidence": {
"type": "array",
"maxItems": 16,
"items": {
"$ref": "common.schema.json#/$defs/evidence_ref"
}
},
"external": {
"type": "boolean",
"description": "True when the action leaves the tenant (message, network call, deploy). External side effects always need an approval record regardless of autonomy level."
}
}
},
"status": {"type": "string", "enum": ["pending", "approved", "denied", "expired", "cancelled"]},
"status": {
"type": "string",
"enum": [
"pending",
"approved",
"denied",
"expired",
"cancelled"
]
},
"decision": {
"type": "object",
"additionalProperties": false,
"required": ["choice", "by", "at"],
"required": [
"choice",
"by",
"at"
],
"properties": {
"choice": {"type": "string", "enum": ["once", "session", "always", "deny"]},
"by": {"$ref": "common.schema.json#/$defs/actor"},
"at": {"$ref": "common.schema.json#/$defs/timestamp"}
"choice": {
"type": "string",
"enum": [
"once",
"session",
"always",
"deny"
]
},
"by": {
"$ref": "common.schema.json#/$defs/actor"
},
"at": {
"$ref": "common.schema.json#/$defs/timestamp"
}
}
},
"requested_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"expires_at": {"$ref": "common.schema.json#/$defs/timestamp"}
"requested_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"expires_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"idempotency_key": {
"$ref": "common.schema.json#/$defs/idempotency_key"
}
}
},
"cancellation_receipt": {
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "run_id", "requested_by", "requested_at", "outcome", "side_effects"],
"required": [
"schema",
"id",
"run_id",
"requested_by",
"requested_at",
"outcome",
"side_effects"
],
"properties": {
"schema": {"const": "hux.cancel_receipt.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"run_id": {"type": "string", "minLength": 1, "maxLength": 120},
"requested_by": {"$ref": "common.schema.json#/$defs/actor"},
"requested_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"acknowledged_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"completed_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"outcome": {"type": "string", "enum": ["cancelled", "already_complete", "failed_to_cancel"]},
"schema": {
"const": "hux.cancel_receipt.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"run_id": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"requested_by": {
"$ref": "common.schema.json#/$defs/actor"
},
"requested_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"acknowledged_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"completed_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"outcome": {
"type": "string",
"enum": [
"cancelled",
"already_complete",
"failed_to_cancel"
]
},
"side_effects": {
"type": "array",
"maxItems": 64,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["description", "reverted"],
"required": [
"description",
"reverted"
],
"properties": {
"description": {"type": "string", "minLength": 1, "maxLength": 280},
"reverted": {"type": "boolean"},
"evidence": {"$ref": "common.schema.json#/$defs/evidence_ref"}
"description": {
"type": "string",
"minLength": 1,
"maxLength": 280
},
"reverted": {
"type": "boolean"
},
"evidence": {
"$ref": "common.schema.json#/$defs/evidence_ref"
}
}
}
},
"conversation_id": {
"$ref": "common.schema.json#/$defs/id"
}
}
},
"budget_state": {
"type": "object",
"additionalProperties": false,
"required": [
"schema",
"run_id",
"spent",
"limits",
"exhausted"
],
"properties": {
"schema": {
"const": "hux.budget_state.v1"
},
"run_id": {
"type": "string",
"maxLength": 120
},
"spent": {
"type": "object",
"additionalProperties": false,
"properties": {
"tokens": {
"type": "integer",
"minimum": 0
},
"tool_calls": {
"type": "integer",
"minimum": 0
},
"wall_clock_seconds": {
"type": "integer",
"minimum": 0
},
"delegations": {
"type": "integer",
"minimum": 0
},
"spend_units": {
"type": "integer",
"minimum": 0
},
"subagents": {
"type": "integer",
"minimum": 0
}
}
},
"limits": {
"$ref": "#/$defs/policy/properties/budgets"
},
"exhausted": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"enum": [
"tokens_per_run",
"tool_calls_per_run",
"wall_clock_seconds",
"delegations_per_run",
"spend_units",
"subagents_per_run"
]
}
}
}
}
},
"oneOf": [
{"$ref": "#/$defs/policy"},
{"$ref": "#/$defs/approval"},
{"$ref": "#/$defs/cancellation_receipt"}
{
"$ref": "#/$defs/policy"
},
{
"$ref": "#/$defs/approval"
},
{
"$ref": "#/$defs/cancellation_receipt"
},
{
"$ref": "#/$defs/budget_state"
}
]
}

View File

@ -7,63 +7,193 @@
"project": {
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "owner", "name", "tags", "pinned", "archived", "created_at", "updated_at"],
"required": [
"schema",
"id",
"owner",
"name",
"tags",
"pinned",
"archived",
"created_at",
"updated_at",
"revision"
],
"properties": {
"schema": {"const": "hux.project.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"owner": {"$ref": "common.schema.json#/$defs/user_ref"},
"name": {"type": "string", "minLength": 1, "maxLength": 120},
"description": {"type": "string", "maxLength": 2000},
"tags": {"$ref": "common.schema.json#/$defs/tags"},
"pinned": {"type": "boolean"},
"archived": {"type": "boolean"},
"default_mode": {"type": "string", "enum": ["fast", "thoughtful", "research", "create", "private"]},
"memory_scope_id": {"$ref": "common.schema.json#/$defs/id"},
"created_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"updated_at": {"$ref": "common.schema.json#/$defs/timestamp"}
"schema": {
"const": "hux.project.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"owner": {
"$ref": "common.schema.json#/$defs/user_ref"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"description": {
"type": "string",
"maxLength": 2000
},
"tags": {
"$ref": "common.schema.json#/$defs/tags"
},
"pinned": {
"type": "boolean"
},
"archived": {
"type": "boolean"
},
"default_mode": {
"type": "string",
"enum": [
"fast",
"thoughtful",
"research",
"create",
"private"
]
},
"memory_scope_id": {
"$ref": "common.schema.json#/$defs/id"
},
"created_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"updated_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"revision": {
"$ref": "common.schema.json#/$defs/revision"
}
}
},
"conversation": {
"type": "object",
"additionalProperties": false,
"required": ["schema", "id", "owner", "title", "tags", "pinned", "archived", "artifact_ids", "created_at", "updated_at"],
"required": [
"schema",
"id",
"owner",
"title",
"tags",
"pinned",
"archived",
"artifact_ids",
"created_at",
"updated_at",
"revision"
],
"properties": {
"schema": {"const": "hux.conversation.v1"},
"id": {"$ref": "common.schema.json#/$defs/id"},
"owner": {"$ref": "common.schema.json#/$defs/user_ref"},
"project_id": {"$ref": "common.schema.json#/$defs/id"},
"title": {"type": "string", "minLength": 1, "maxLength": 200},
"tags": {"$ref": "common.schema.json#/$defs/tags"},
"pinned": {"type": "boolean"},
"archived": {"type": "boolean"},
"mode": {"type": "string", "enum": ["fast", "thoughtful", "research", "create", "private"]},
"schema": {
"const": "hux.conversation.v1"
},
"id": {
"$ref": "common.schema.json#/$defs/id"
},
"owner": {
"$ref": "common.schema.json#/$defs/user_ref"
},
"project_id": {
"$ref": "common.schema.json#/$defs/id"
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"tags": {
"$ref": "common.schema.json#/$defs/tags"
},
"pinned": {
"type": "boolean"
},
"archived": {
"type": "boolean"
},
"mode": {
"type": "string",
"enum": [
"fast",
"thoughtful",
"research",
"create",
"private"
]
},
"branch": {
"type": "object",
"additionalProperties": false,
"required": ["parent_conversation_id", "branch_point_message_id"],
"required": [
"parent_conversation_id",
"branch_point_message_id"
],
"properties": {
"parent_conversation_id": {"$ref": "common.schema.json#/$defs/id"},
"branch_point_message_id": {"type": "string", "minLength": 1, "maxLength": 120}
"parent_conversation_id": {
"$ref": "common.schema.json#/$defs/id"
},
"branch_point_message_id": {
"type": "string",
"minLength": 1,
"maxLength": 120
}
}
},
"artifact_ids": {"type": "array", "maxItems": 500, "uniqueItems": true, "items": {"$ref": "common.schema.json#/$defs/id"}},
"last_message_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"created_at": {"$ref": "common.schema.json#/$defs/timestamp"},
"updated_at": {"$ref": "common.schema.json#/$defs/timestamp"}
"artifact_ids": {
"type": "array",
"maxItems": 500,
"uniqueItems": true,
"items": {
"$ref": "common.schema.json#/$defs/id"
}
},
"last_message_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"created_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"updated_at": {
"$ref": "common.schema.json#/$defs/timestamp"
},
"revision": {
"$ref": "common.schema.json#/$defs/revision"
}
}
},
"search_index": {
"type": "object",
"description": "Fields the tenant indexes; the UI may not assume anything else is searchable.",
"additionalProperties": false,
"required": ["conversation"],
"required": [
"conversation"
],
"properties": {
"conversation": {"type": "array", "items": {"type": "string", "enum": ["title", "tags", "message_text", "artifact_titles", "project_name"]}}
"conversation": {
"type": "array",
"items": {
"type": "string",
"enum": [
"title",
"tags",
"message_text",
"artifact_titles",
"project_name"
]
}
}
}
}
},
"oneOf": [
{"$ref": "#/$defs/project"},
{"$ref": "#/$defs/conversation"}
{
"$ref": "#/$defs/project"
},
{
"$ref": "#/$defs/conversation"
}
]
}

View File

@ -9,7 +9,6 @@ refuse the transitions that would let merged/built/deployed be confused.
from __future__ import annotations
import copy
import importlib.util
import json
import sys
import tomllib
@ -20,21 +19,13 @@ import pytest
import yaml
ROOT = Path(__file__).resolve().parents[2]
SCRIPTS = ROOT / "services" / "hermes" / "scripts"
FOUNDATION = ROOT / "dockerfiles" / "hermes-hux-foundation"
CONTRACTS = ROOT / "services" / "hermes" / "contracts" / "hux"
def _load(name: str):
spec = importlib.util.spec_from_file_location(name, SCRIPTS / f"{name}.py")
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
contracts = _load("hux_contracts")
policy = _load("hux_policy")
if str(FOUNDATION) not in sys.path:
sys.path.insert(0, str(FOUNDATION))
from hux import contracts, rules as policy # noqa: E402
SCHEMAS = contracts.load_all()
EXAMPLES = {path.stem: json.loads(path.read_text()) for path in sorted((CONTRACTS / "examples").glob("*.json"))}
NOW = datetime(2026, 8, 23, 12, 0, tzinfo=timezone.utc)
@ -166,6 +157,7 @@ def test_capability_matrix_shape():
for level, row in matrix.items():
assert set(row) == set(policy.CAPABILITIES)
assert row["deploy"] == "ask", level
assert row["external_side_effect"] == "ask", level
assert row["read_files"] == "allow"
assert matrix["ask_first"]["shell"] == "ask"
assert matrix["safe"]["network"] == "deny"