Hellas commits exact work, runs it locally or over an adversarial network, and
binds the result into a signed transcript. Execution identity is one canonical
ProgramManifest:
(evaluator, adaptor, content-addressed application root)
The two application identifiers are exact opaque strings. Hellas does not parse versions, negotiate compatibility, or consult a model/package registry.
The application owns the meaning of its root. The two currently modelled shapes intentionally have different trust boundaries:
| Application | Root and trusted computation | Outside that guarantee |
|---|---|---|
hellas/catena-gpu-0.0.1, causal-lm-0.0.1 |
Exact Catena program, entrypoint, content-addressed static objects and borrowed slices, state sizing, vocabulary, capacity, and token-native invocation/result | Model acquisition, provider GPU/backend choice, tokenizer, chat template, text decoding, and API presentation |
hellas/fetch-0.0.1, codex-responses-0.0.1 or openai-responses-0.0.1 |
A strict stateless Responses field set; typed reconstruction of provider-shaped JSON; stream=true and upstream store=false; the exact official Codex or OpenAI HTTPS endpoint; no redirects; and SSE response projection |
The truth, correctness, and availability of the adversarial upstream service; provider route label; credentials/auth-file location; and access policy |
For causal LM work, the request and transcript bind the manifest, input token
IDs, maximum output, explicit stop IDs, output token IDs, and termination.
--tokenizer is a caller-side lens that encodes prompt text and decodes the
verified IDs; it is not part of the Catena kernel claim. Hellas infers no stop
tokens from it.
Fetch is different. Its purpose is to attest the exact transformation around a remote request, so structuring and destructuring are inside that application's trusted path rather than presentation performed outside it. This proves which checks and transformation ran, not that the remote service's claims are true.
A platform-backed Assurance authenticates the Fetch application. The
ProducerSigned mode authenticates only the producer key and signed transcript;
it does not authenticate a running binary. The Responses-facing store switch
controls Hellas Courtesy transcript retention. The upstream call made by the
attested app is always stateless and sends store=false. This first sealed adaptor accepts text
and client-executed function tools; it rejects provider-account references,
provider-side tools, file/image inputs, and unknown top-level fields.
Retention is opt-in. --retain (or a request-level retain=true / store=true)
publishes prompt- and token-bearing artifacts through content-addressed Courtesy
GetArtifact; a digest is an address, not an authorization capability. Omission
keeps the execution ephemeral. This is not a deletion promise for an accepted paid job: the signed
prepared input remains in that channel's recovery/evidence journal as required
to resume safely after a crash.
Human-readable settings live in examples/. Their paths are
local acquisition hints; canonical environment bytes contain only identities,
lengths, and ABI data.
hellas-cli environment build \
--program model.hex \
--settings examples/smollm2.environment.toml \
--out smollm2.environment
hellas-cli environment inspect --environment smollm2.environment
# Prove one environment and all of its referenced content are provider-ready.
hellas-cli environment verify \
--environment smollm2.environment \
--content-root /srv/hellas/content \
--content-index /var/lib/hellas/content.indexFor llm and gateway, the caller selects the environment trust anchor before
any route starts. By default, the exact local --environment file bytes are
that anchor and the CLI derives their manifest ID. When a manifest ID was
distributed separately, pass --manifest-id <CONTENT_ID>; a file deriving
a different ID is rejected before network or GPU work. A provider never selects
either value.
A provider indexes ordinary runtime files and can accept any locally satisfiable supported environment; it need not register a model name:
hellas-cli --software-root serve \
--execute-policy any \
--content-root /srv/hellas/contentQuoteTokens strictly decodes the submitted manifest. The first binding opens
its root by exact local content ID and verifies every declared program/static
object; later quotes for that exact manifest may reuse the immutable verified
binding without reopening or rehashing those files. Quoting neither fetches nor
compiles. The authorized worker is the final availability and integrity
boundary: before nonresident content enters the safe runtime, it reopens the
descriptor and enforces the exact ID and length; an already-resident exact
mapping is reused. The provider then compiles the Catena source for its visible
GPU device. Verified static files are lent by descriptor to a bounded
persistent safe-runtime session, so an already prepared program and weights are
reused across requests until the session is recycled or the service restarts.
There is no client-supplied gfx target or provider architecture allow-list.
Providers independently bound compilation with --gpu-compile-timeout-secs
and each complete generation with --gpu-execution-timeout-secs; expiry kills
the isolated worker process group and the next request starts a fresh session.
--gpu-max-generation-capacity is additionally capped at 524288 tokens so a
retained token transcript fits Hellas's 4 MiB unary artifact transport.
Indexed provider content is an immutable local-cache assumption. Hellas pins the verified read-only descriptor and detects path/inode replacement; it does not defend against a separate local process that already holds a writable descriptor to the same inode and mutates it concurrently.
Fetch route names are operator-defined routing labels. The sealed destination selects the trusted adaptor, fixed official endpoint, no-redirect HTTP driver, and response projector as one unit. A configuration cannot supply a URL or claim a different adaptor identity.
For an OpenAI Responses route, first create the caller identity and obtain its producer public key:
hellas-cli --identity caller.identity --software-root identity init
CALLER_KEY=$(hellas-cli --identity caller.identity producer-key show |
awk '$1 == "public_key:" { print $2 }')
export CALLER_KEY
export OPENAI_API_KEY='<provider-local credential>'Write fetch.json; the model/output limits shown are optional:
{
"routes": [{
"service": "openai",
"method": "responses",
"destination": {
"type": "openai-responses",
"api_key_env": "OPENAI_API_KEY"
},
"capabilities": {
"models": ["gpt-5.5"],
"max_output_tokens": 4096
}
}],
"callers": [{
"public_key": "REPLACE_WITH_CALLER_KEY",
"routes": [{
"service": "openai",
"method": "responses",
"models": ["gpt-5.5"],
"max_output_tokens": 512
}]
}]
}Replace REPLACE_WITH_CALLER_KEY with $CALLER_KEY, then start the provider
and obtain its node and enrollment IDs through a trusted channel:
hellas-cli --identity provider.identity --software-root identity init
NODE_ID=$(hellas-cli --identity provider.identity identity show-node-id)
ENROLLMENT_ID=$(hellas-cli --identity provider.identity identity show-enrollment-id)
hellas-cli --identity provider.identity --software-root serve \
--port 49152 \
--fetch-config fetch.jsonThe OpenAI sealed manifest ID is
a4ff1dbe22fe5d6888258bd95d21a288855c11d40b59c86ad834ab747033e8e8.
Run one strict provider-shaped request from another terminal:
hellas-cli --identity caller.identity --software-root fetch "$NODE_ID" \
--node-addr 127.0.0.1:49152 \
--provider "$ENROLLMENT_ID" \
--service openai \
--method responses \
--execution-environment openai-responses \
--payload '{"model":"gpt-5.5","input":"Say hello","stream":true,"store":false,"max_output_tokens":32}'Output is JSON Lines of semantic response events followed by one terminal event; raw upstream SSE is never the signed result. The caller's exact input bytes are signed, but the trusted app parses and reconstructs a fresh upstream body before egress. Open, ticket creation, execution, and output-key verification all remain on the same confidentially opened transport.
The Codex alternative uses destination type codex-responses, a local
auth_path populated by hellas-cli codex-auth login, and sealed manifest ID
82ebed7724b614bfcca6082924710098821cafc95789f136f770667e16ef9785.
In both cases, ProducerSigned proves only the key and transcript. Claiming the
trusted app itself requires a platform-backed assurance.
The gateway requires the same canonical causal-LM environment and an explicit
presentation tokenizer. --model is only an API response label; when omitted,
the manifest ID is used.
hellas-cli --software-root gateway \
--local \
--environment smollm2.environment \
--content-root /srv/hellas/content \
--tokenizer /srv/hellas-presentation/tokenizer.json \
--model smollm2-135mIt binds loopback by default. Non-loopback listening requires --allow-remote
and --bearer-token-file FILE; the private credential file is created once and
reused across restarts. Without a file, a fresh credential is shown on the
controlling terminal. The causal-LM backend accepts plain text at
/v1/completions and /v1/responses. Set --chat-template to enable the shared
model adapter for chat, reasoning and tool calls supported by that model.
The proxy and attested Fetch Responses backends have their own explicit
semantics. --responses-backend changes only /v1/responses; every other
route remains bound to the causal-LM environment, so --environment and
--tokenizer are still required.
The exposed routes are:
POST /v1/completions
POST /v1/responses
POST /v1/chat/completions
POST /v1/messages
Monitor discovery and peer health with hellas-cli monitor --timeout-secs 30.
The chain feature provides chain query, chain open, and chain close.
The indexer feature adds chain indexer follow; the validator feature adds
chain validator config, chain validator run, and
chain validator check-config.
chain query --rpc URL supports latest-block, state-root, finalization,
finalized-block, coin, edge, validators, coins-by-owner, and
edges-by-owner.
chain open reads each 32-byte secret scalar from --maker-key FILE and
--taker-key FILE; --maker-auth and --taker-auth accept webauthn or
native. Funding IDs use repeatable or comma-separated --maker-funding and
--taker-funding. Terms use --protocol, --timeout, and repeatable or
comma-separated --timeout-payout SETTLEMENT_KEY:VALUE. --terms-out FILE
writes the canonical reveal for a later timeout close.
chain close --kind mutual takes repeatable or comma-separated
--payout SETTLEMENT_KEY:VALUE plus both key and auth pairs. --kind timeout
takes the same committed payouts and --terms-file FILE, with no signer
options.
Stored ownership uses raw, untagged 33-byte settlement keys. P-256 and
secp256k1 keys can own stored coins. Legacy Transfer and MergeCoin
verification remains P-256-only, and genesis owner strings must decode as
P-256 keys when the validator config is loaded.
Given two P-256 key files whose settlement keys each own a 100-value coin:
RPC=ws://127.0.0.1:56946
MAKER_KEY=maker.key
TAKER_KEY=taker.key
MAKER_OWNER=maker-settlement-key
TAKER_OWNER=taker-settlement-key
MAKER_COIN=maker-coin-id
TAKER_COIN=taker-coin-id
OPEN=$(
cargo run --no-default-features --features chain -- chain open \
--rpc "$RPC" \
--maker-key "$MAKER_KEY" --maker-auth webauthn \
--taker-key "$TAKER_KEY" --taker-auth webauthn \
--maker-funding "$MAKER_COIN" --taker-funding "$TAKER_COIN" \
--protocol 1 --timeout 1000 \
--timeout-payout "$MAKER_OWNER:100" \
--timeout-payout "$TAKER_OWNER:100"
)
EDGE_ID=$(printf '%s\n' "$OPEN" | awk '$1 == "edge_id" { print $2 }')
# After the open finalizes:
PAYLOAD=$(cargo run --no-default-features --features chain -- \
chain query --rpc "$RPC" latest-block | awk '$1 == "payload" { print $2 }')
cargo run --no-default-features --features chain -- \
chain query --rpc "$RPC" edge --object-id "$EDGE_ID" --payload "$PAYLOAD"
cargo run --no-default-features --features chain -- chain close \
--rpc "$RPC" --edge-id "$EDGE_ID" --kind mutual \
--payout "$MAKER_OWNER:100" --payout "$TAKER_OWNER:100" \
--maker-key "$MAKER_KEY" --maker-auth webauthn \
--taker-key "$TAKER_KEY" --taker-auth webauthn
# After the close finalizes:
cargo run --no-default-features --features chain -- \
chain query --rpc "$RPC" coins-by-owner --owner "$MAKER_OWNER"
cargo run --no-default-features --features chain -- \
chain query --rpc "$RPC" coins-by-owner --owner "$TAKER_OWNER"The main outputs are .#cli (network client/node/gateway), .#cli-catena
(x86_64 Linux plus the Catena safe GPU runtime), and .#cli-validator.
Hellas imports only catena-lang from the Catena workspace, pinned to a
published Git revision in Cargo.toml and Cargo.lock. Nix vendors the same
locked dependency; a sibling Catena checkout is not required.
Enter an x86_64 Linux GPU development shell with:
nix develop .#rocm --no-write-lock-file # AMD
nix develop .#cuda --no-write-lock-file # NVIDIACatena selects a runtime inside an isolated worker. Use serve --gpu-backend auto|hip|cuda to choose one; auto chooses CUDA on a NixOS NVIDIA
host and HIP otherwise.
The NixOS module provisions the matching toolchain and device access:
services.hellas = {
enable = true;
executePolicy = "any";
gpuBackend = "cuda"; # or "hip" / "auto"
contentRoots = [ "/srv/hellas/content" ];
};Keep models and compiler artifacts outside /nix/store; they are runtime data.
Fetch providers likewise use runtime files: set fetchConfigFile to the JSON
configuration path and environmentFile to a systemd environment file holding
provider-local secrets such as OPENAI_API_KEY. Do not put either file in a
Nix expression or in the store. In particular, never interpolate the file as a
Nix path and never use builtins.readFile on it: both operations expose its
contents during evaluation, before any module assertion or runtime validation
can protect it. Retained Fetch evidence is bounded across both
completed transcripts and indeterminate running markers by
fetchRetainedTranscriptCapacity (default 1024; zero disables new retention).
fetchReplayMaxInFlight separately bounds replay consumers (default 16), and a
slot remains occupied until its event stream is drained or dropped. The
retention capacity is persisted per transcript-store root so processes sharing
one root cannot disagree. Stop every such process before changing the capacity
or removing its metadata; existing transcripts and running markers are never
deleted, and an already over-cap root still starts and replays while refusing
new retention.
On Darwin, the Home Manager launch agent remains network-only but supports the same runtime-secret boundary:
programs.hellas = {
enable = true;
serve = {
enable = true;
fetchConfigFile = "/Users/alice/.config/hellas/fetch.json";
environmentFile = "/Users/alice/.config/hellas/provider.env";
};
};Provision the environment file outside Nix and restrict it to the user, with no
group or other permission bits. The runtime wrapper resolves parent symlinks,
rejects the Nix store, opens the final component without following symlinks and
without blocking on special files, then requires that same opened descriptor to
name a regular file owned by the agent's effective user. Its
grammar is deliberately small: blank lines and # comments in column one are
allowed; every other line is NAME=VALUE, with names matching
[A-Za-z_][A-Za-z0-9_]*. Values are literal, so spaces, #, and = are kept
and quotes, escapes, substitutions, and shell commands have no special
meaning. Duplicate names, malformed lines, CR/NUL bytes, or any failed security
check stop the agent before Hellas runs. The launchd plist contains the absolute
file path, never its contents. As above, never use Nix interpolation or
builtins.readFile for this file; validation cannot undo an evaluation-time
secret leak.
Retained Evaluate artifacts are separately bounded by
evaluateRetainedExecutionCapacity (default 1024; zero disables new retained
completions). Each unique execution reserves one persistent slot before its
graph is published; a crash can leave that slot and up to eight canonical
objects behind, but cannot grow the store past the configured execution bound.
The Evaluate root is exclusively locked for the provider lifetime, and its
capacity metadata must match on every restart.
Work on the kernel Quint models:
nix develop .#kernel
nix run .#check-kernel-models
nix run .#check-kernel-model-verifyThe default docker output is a network-only node image. It contains no local Catena or
GPU runtime and is tagged ghcr.io/hellas-ai/hellas:network. The derivation
streams a Docker archive to stdout:
$(nix build .#docker --print-out-paths) | docker load
nix run .#docker-push # network image onlyGPU images include Catena and the corresponding runtime compiler:
$(nix build .#docker-cuda --print-out-paths) | docker load
$(nix build .#docker-hip --print-out-paths) | docker load
# NVIDIA with a configured CDI device specification:
docker run --rm --device nvidia.com/gpu=all \
-v hellas-state:/var/lib/hellas -v /srv/hellas/content:/content:ro \
ghcr.io/hellas-ai/hellas:cuda --software-root --content-root /content
# AMD:
docker run --rm --device /dev/kfd --device /dev/dri \
-v hellas-state:/var/lib/hellas -v /srv/hellas/content:/content:ro \
ghcr.io/hellas-ai/hellas:hip --software-root --content-root /contentBoth GPU images default to serve with their matching backend. The host supplies
the GPU driver and model content stays on runtime volumes. On x86_64 Linux,
nix run .#docker-push-all publishes the network, CUDA, and HIP images.
A GPU asset owner uploads verified weights once and execution workers map them read-only. Worker replacement retains weights; asset pressure recreates the owner. HIP resident sharing requires version 7.15 or newer.
Inference reuse is opt-in: --output-cache off (the default), record
(reuse the first successful result, record misses), or replay-only (fail
on a miss without inference). These are local trust-on-first-use recordings,
independent of the protocol's public --retain setting. Enabling recording
persists inference outputs even for otherwise ephemeral requests.
Caching applies to native CLI/library execution, gateway requests, and executor requests from uncached clients. Executor replay verifies its original signed evidence before signing for the new ticket; client replay preserves provenance. Only complete successful streams are recorded, and recording failures are reported rather than silently losing the material required for later replay.
| Kind | Input identity |
|---|---|
| Evaluate | Catena execution identity: environment, input state, prompt token IDs and decode policy |
| Responses proxy | Endpoint and effective forwarded body after model/stream normalization |
| Inference Fetch | Sealed Responses environment, service, method and body, excluding caller signatures |
General Fetch operations bypass recording and are refused in replay-only mode. Agent tool execution is not cached. Shadow verification requires live execution.
The cache uses the existing Hellas store (--store-dir, HELLAS_STORE_DIR,
or ~/.hellas/store): Xet-addressed objects and a DAG-CBOR inference index.
One process owns the writable index; administer its live store through RPC.
Offline read-only opens are snapshots. Clear/remove/prune remove mappings, not
shared objects or executor ticket history, and prevent older in-flight work
from republishing. Replay-only administration is read-only.
Client-side Fetch recordings (CLI/gateway) share keys with executor recordings,
but omit the signed evidence required for executor replay. Reusing that store
with serve can therefore fail with a missing signed field, in both record
and replay-only modes; invalid entries never trigger live fallback. Remove
the affected entry with
hellas-cli output-cache --kind fetch --key "$IDENTITY" remove, then let the
executor record it afresh. For a running writer, add --socket or --node-id
as described below. Executor recordings remain readable by clients.
hellas-cli --output-cache record gateway --responses-backend proxy --wrap opencode
hellas-cli --output-cache replay-only gateway --responses-backend proxy --wrap opencode
hellas-cli output-cache --kind proxy list
hellas-cli output-cache --kind proxy --key "$IDENTITY" show
hellas-cli output-cache --json stats
hellas-cli output-cache --kind proxy prune --max-entries 1000 --dry-run
hellas-cli output-cache export --to ./agent-recordingsControl is ordinary transport-independent RPC with separate admin grants.
For an owner-only Unix socket (also supported by serve):
mkdir -m 700 ./hellas-control
hellas-cli --output-cache record --control-socket ./hellas-control/control.sock \
gateway --responses-backend proxy
hellas-cli output-cache --socket ./hellas-control/control.sock --kind proxy clearFor remote node administration, explicitly allow the administrator's Iroh node ID, then use that administrator's existing identity to connect:
hellas-cli --output-cache record serve --admin-peer "$ADMIN_NODE_ID"
hellas-cli --identity ./admin.identity output-cache --node-id "$NODE_ID" clearAll management commands accept either --socket or --node-id (with optional
--node-addr); reset aliases clear. Without either they operate offline.
Remote admin is disabled by default and is not publicly advertised. An
authenticated peer is not an administrator unless explicitly granted access.
An admin grant permits reading/exporting every cached transcript as well as
clearing it; those transcripts may contain private prompts, source, or outputs.
Embedders compose CacheControlServer(CacheController) with Authorized and
AdminPolicy, reusing the existing dispatcher and carriers. WebSocket and
serial hosts must authenticate their connection before supplying its identity;
raw connectivity grants nothing. The shared framed byte-stream adapter also
accepts serial I/O. Unix checks OS ownership; its socket is mode 0600 under
an owner-only directory, and existing paths are not overwritten.
For a Linux Nix agent build, use the overlay's pkgs.hellasLib.agent helpers:
let
agent = pkgs.hellasLib.agent;
inputs = {
name = "agent-change";
workspace = ./source;
prompt = builtins.readFile ./prompt.txt;
model = "your-provider-model";
hellas = pkgs.hellas.cli;
opencode = pkgs.opencode;
packages = [ pkgs.rustc pkgs.cargo ]; # tools available to the agent
gatewayArgs = [ "--responses-backend" "proxy" ];
};
in {
record = agent.mkAgentRecord inputs;
result = agent.mkAgentRun (inputs // { cache = ./agent-recordings; });
}Run the recorder executable with a writable store directory outside the Nix
build, with provider credentials in its runtime environment. Export a snapshot
and use it as cache. mkAgentRun starts a replay-only gateway inside the
sandbox, runs OpenCode, and returns its resulting workspace. Both phases use
/build/workspace, the same pinned tools/configuration, and an explicit system
prompt date (default 1970-01-01). Changing inputs can produce a cache miss;
re-record them instead of giving the build network access. A replay reruns
tools and is only reproducible when those tools are deterministic too.
The helper uses OpenCode's generic OpenAI Responses client. Its endpoint must
accept that request shape; the stricter sealed codex-responses Fetch contract
is not a drop-in target. A zero OpenCode exit status alone is not success: the
runner also requires a completed session and rejects error events.
Recordings and generated files can contain private source or model output. Do not put credentials in Nix expressions, and do not publish recordings without reviewing them: Nix store contents are normally readable by all local users.
Available in the development shell:
cargo audit # security advisories
cargo outdated --workspace --root-deps-only # outdated deps
cargo update --workspace # update Cargo.lockThe otel Cargo feature is opt-in across the CLI (including Fetch, node,
gateway, indexer and validator commands). Default Nix packages, including
public musl builds, disable Hellas telemetry exporters. Request instrumentation
uses shared enabled/no-op implementations; ordinary operational logs remain.
Transitive dependencies still include metrics collection and telemetry SDK crates;
disabling otel does not remove those dependencies.
The exported NixOS, nix-darwin and Home Manager modules share otel options:
# NixOS node and gateway:
services.hellas.otel = {
enable = true;
collectorEndpoint = "http://127.0.0.1:4318";
sampleRate = 1.0;
};
# nix-darwin CLI or Home Manager CLI (including Darwin's launchd agent):
programs.hellas.otel.collectorEndpoint = "http://127.0.0.1:4318";
# Validator clusters use services.hellas-chain-validators.<name>.otel.A configured endpoint enables the feature on the module's default package;
otel.enable = false explicitly selects the build without telemetry. Custom
packages remain the caller's choice. collectorEndpoint is the standard OTLP
HTTP/protobuf base URL for traces and metrics; the existing endpoint option
continues to accept an exact trace URL. Home Manager and nix-darwin scope
variables to hellas-cli, rather than enabling unrelated applications.
Home Manager's programs.hellas.serve supplies the Darwin user service.
Direct package users can use cli.override { otel = true; } (also
cli-catena and cli-validator) and the standard OTEL_* environment variables.
One CLI SDK lifecycle owns traces and metrics, including validators. Native
operations follow the GenAI Development conventions at
c88d504ab3d9879f8e50d3cc87e69775e11db234; these conventions are still evolving.
Hellas-specific work/payment metadata uses hellas.*, with W3C TraceContext
in RPC metadata and HTTP headers. Prompts, model output, tool arguments and
credentials are not recorded. Log events stay in the local log sinks, rather
than being duplicated into OTLP. Normal shutdown flushes both providers;
existing forced-exit paths cannot guarantee a final flush.