Work stops here — the deploy is blocked on Maven, which is under construction. Records what shipped, the five behavioural changes to read before deploying (notably that hexisd now refuses to start without a token and that execute requires Nexus liveness), what is blocked versus doable now, and the limitations accepted along the way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uea55zaiWuEByEDC4UBSdd
Hexis
Capability registry and guarded execution service for the Nexus / Praxis / Hexis
ecosystem (see ECOSYSTEM-SPEC.md §4 in the sibling repo root).
Hexis is the only component allowed to act on infrastructure. It holds a registry
of named capabilities (workspace.docker.restart, …), each bound to a registered
provider implementation — never to a shell string — and mediates every execution
behind a set of guards:
- a capability must be
enabled; - capabilities marked
requires_confirmationneed a valid, unexpired confirmation matching the capability, target and argument hash (default TTL applies); - one in-flight execution per
(capability, target_entity_id)— a second attempt gets409; - a wall-clock timeout per capability; on timeout the outcome is
unknown(neverfailed, never auto-retried).
Its place in the ecosystem:
- Nexus (
:9740) owns entities. Hexis resolves free-text targets to entity IDs through Nexus rather than inventing them. - Praxis (
:8989) owns items/incidents and consumes execution correlation. - Hexis (
:9741) owns capabilities, confirmations and executions. - Consumers today: Maven (voice + mavweb) over the HTTP API, plus MCP clients via the stdio adapter.
Actual work is performed by providers. The live one is workspace_mcp, which
proxies an allowlisted set of tools from a Workspace MCP HTTP server (docker,
filesystem, …). There is also a systemd provider stub, which no capability
currently registers.
Status note: this repo has a candid engineering review in
REVIEW-2026-07-30.md. Read it before trusting the guards — several are documented there as incomplete at the time of writing.
Build
Go 1.25+. No CGO (sqlite is the pure-Go modernc.org/sqlite).
make build # -> bin/hexisd, bin/hexisctl
make check # gofmt check + go vet + go test
Individual targets: make test, make vet, make fmt, make fmt-check,
make tidy, make clean, make help.
Run locally
export HEXIS_API_TOKEN=dev-token # required; hexisd refuses to start without it
make run # bin/hexisd -http localhost:9741 -data ~/.local/share/hexis
That runs registry + execution API with no workspace provider, so nothing is executable — useful for API work. To wire the real provider:
bin/hexisd \
-http localhost:9741 \
-data ~/.local/share/hexis \
-workspace-url http://localhost:9930 \
-workspace-allowlist etc/workspace-allowlist.yaml
On startup Hexis discovers the workspace tools and registers a capability for each
allowlisted entry. Discovery failure is a warning, not fatal; a missing or
unreadable allowlist file is fatal when -workspace-url is set.
MCP stdio mode (same storage, same engine, tools hexis.list_capabilities,
hexis.inspect_capability, hexis.resolve_target, hexis.execute,
hexis.execution_status):
make run-mcp # bin/hexisd -mcp -data ~/.local/share/hexis
bin/hexisctl is a thin CLI over the HTTP API:
bin/hexisctl health
bin/hexisctl capability list [--entity ENTITY_ID]
bin/hexisctl capability show <id>
bin/hexisctl exec <capability-id> <target-entity-id> [--args JSON] [--idempotency KEY]
bin/hexisctl execution <id>
Configuration
Flags (all optional) on hexisd:
| Flag | Default | Meaning |
|---|---|---|
-http |
localhost:9741 |
HTTP listen address |
-data |
$HOME/.local/share/hexis |
Data dir; SQLite lives at <data>/hexis.db |
-mcp |
false |
Run the MCP stdio adapter instead of the HTTP server |
-workspace-url |
$WORKSPACE_MCP_URL |
Workspace MCP HTTP API URL, e.g. http://localhost:9930. Empty ⇒ provider disabled |
-workspace-allowlist |
<data>/workspace-allowlist.yaml |
Tool allowlist YAML (repo copy: etc/workspace-allowlist.yaml) |
-nexus |
$HEXIS_NEXUS_URL, else http://localhost:9740 |
Nexus base URL, used by hexis.resolve_target |
Environment:
WORKSPACE_MCP_URL— fallback for-workspace-url.HEXIS_NEXUS_URL— fallback for-nexus.HEXIS_URL— read byhexisctlonly; defaults tohttp://localhost:9741.HEXIS_API_TOKEN— shared bearer token; required byhexisd, and also read byhexisctl(and bypkg/clientviaWithToken) to authenticate its calls.
Authentication. The whole /api/v1/ surface sits behind a shared bearer
token read from HEXIS_API_TOKEN; /health and /ready stay open for probes.
It fails closed: hexisd refuses to start without the variable set, and the
middleware answers 503 rather than serving unauthenticated if it is somehow
empty. Callers send Authorization: Bearer <token>; a missing or wrong token
gets 401.
export HEXIS_API_TOKEN="$(openssl rand -hex 32)"
curl -H "Authorization: Bearer $HEXIS_API_TOKEN" localhost:9741/api/v1/capabilities
(This landed while the README was being written — if the details drift, internal/api/handler.go
requireAuth is the source of truth.)
Ports: Hexis 9741, Nexus 9740, Praxis 8989, Workspace MCP 9930.
API surface
All /api/v1/ responses are JSON. A request may send X-Hexis-Version; if
present it must be v1, otherwise the request is rejected with 412. Omitting
the header is allowed.
| Method | Path | Notes |
|---|---|---|
GET |
/health |
liveness |
GET |
/ready |
readiness |
GET |
/api/v1/capabilities |
optional ?entity_id= filter |
POST |
/api/v1/capabilities |
register a capability |
GET |
/api/v1/capabilities/{id} |
|
DELETE |
/api/v1/capabilities/{id} |
|
POST |
/api/v1/confirmations |
{capability_id, target_entity_id, arguments?, requester?} → 201 with the confirmation |
POST |
/api/v1/execute |
{capability_id, target_entity_id, arguments?, confirmation_id?, …}; also reads X-Correlation-ID / X-Causation-ID |
GET |
/api/v1/executions |
history; ?entity_id=, ?since=<seq>, ?limit= (≤100) |
GET |
/api/v1/executions/{id} |
|
GET |
/api/v1/changes |
event feed, ?since=<sequence>, up to 100 events |
Execute status codes: 409 in-flight duplicate, 404 unknown capability, 403
confirmation/enablement failures, 400 otherwise.
Pagination
/api/v1/executions and /api/v1/changes share one cursor convention: since
is an exclusive integer sequence, results come back in ascending order, and
you page by passing the sequence of the last item you saw. Executions carry it
as seq, events as sequence. A full page means "call again"; there is no
separate next-page token, and there is no timestamp cursor.
Capability wire shape
Every producer — the HTTP handler, the MCP adapter and pkg/client — serializes
a capability through the single shape defined in pkg/client/capability.go,
converted by internal/wire. Notable points:
capability_id(the spec §4.1 name) andidare both emitted and always carry the same value.idis a deprecated alias retained for Maven's client; see the compatibility note inpkg/client/capability.gobefore removing it.enabledandrequires_confirmationare always present, including when false. They are derived server-side from the risk tier and are never settable by a caller.
Tests
make test # go test ./...
go test ./internal/execution/ -run TestConfirmation -v
Tests are table-driven Go tests colocated with their packages — currently
internal/api/handler_test.go, internal/execution/{engine,contract}_test.go,
internal/provider/workspace_mcp_test.go. They use temporary SQLite files and
need no running services.
Container and deploy
make docker-build # distroless image, static CGO_ENABLED=0 binary
The image entrypoint is /hexisd -data /data -http 0.0.0.0:9741, so the data
volume must be mounted at /data, HEXIS_API_TOKEN must be in the container
environment, and — when WORKSPACE_MCP_URL is set — the allowlist must be present
at /data/workspace-allowlist.yaml, or the container exits immediately.
Hexis is deployed as part of the ecosystem compose stack, which lives outside this
repo (Maven/deploy/ecosystem/docker-compose.yml, alongside nexus and praxis;
host nginx fronts it). Convenience wrappers:
make compose-up # build + start just the hexis service
make compose-logs
Override COMPOSE_DIR if your checkout layout differs from
../Maven/deploy/ecosystem.