Documentation
HST is a library you link, not a service you call.
HST ships as libhstcore — a stripped shared object with an opaque C ABI. You compile the fixed operator once into an artifact, then open that artifact inside the process that already owns your state and apply sparse deltas in your hot loop. The dense state vector never leaves the process. There is no server, no port, no client library talking over a socket. Run the fit test before you integrate: the six questions there decide whether your workload has the shape HST needs.
Why there is no hosted API
The transport costs a thousand times more than the work.
This is the central engineering fact about HST, and it decides the entire integration story. The delta kernel is very fast in absolute terms — on the reference NUC11 box, twenty timed apply_delta calls measured 0.002 ms of kernel compute each. The HTTP round trip wrapped around those same calls measured 1.75 ms minimum, 2.25 ms median, 4.55 ms maximum. The hop costs roughly a thousand times the compute it is carrying.
It gets worse with size, because the request shape is wrong in a way no amount of tuning fixes. At N=60,000 a single delta step saves 0.851 ms of compute. Measured p50 transport overhead for that same step: in-process ~0 ms, local IPC over stdio 61 ms, REST over localhost 188 ms. The delta itself is about 1.6 KB, but any call boundary that does not share memory has to ship dense x_prev and y_prev — 1.76 MB — and return a dense y_next at 464 KB. Serializing that request alone costs 32 ms.
So the break-even is explicit: a REST deployment only pays for itself if your full recompute already takes more than ~188 ms, and a local sidecar only if it takes more than ~61 ms. Below those thresholds the network eats the entire win and then some. The Docker/REST service was deprecated for production on 2026-07-06 for exactly this reason. We did not replace it with a leaner protocol, a binary codec, or a gRPC variant — the problem is the boundary, not the encoding. Embedded in-process is the only supported deployment.
1. Compile the operator once
Sparse operator A goes in as COO. The compiler emits a binary artifact holding the tile index. This is offline, done once per operator, and its cost is amortized across every subsequent apply.
hst-runtime compile \
--input operator_coo.json \
--output op.json
# writes op.bin
2. Open the artifact
Include hstcore.h, link libhstcore.so (Linux) or libhstcore.dylib (macOS). hst_open returns an opaque handle or NULL, writing the reason into your error buffer. A handle is not thread-safe — use one per thread or stream.
hst_ctx *hst_open(const char *artifact_path,
const char *license_token,
char *errbuf, size_t errbuf_len);
hst_ctx *hst_open_batched(const char *artifact_path,
const char *license_token,
int32_t batch,
char *errbuf, size_t errbuf_len);
3. Apply deltas in your loop
Pass the changed column indices and their values. Returns 0 on success, negative on error. Batch width is 1–32; buffers are lane-interleaved as vals[i*batch+b].
int hst_apply_delta(hst_ctx *ctx,
const int32_t *cols,
const double *vals,
int32_t n,
double *y_out);
/* zero-copy read of current state;
valid until the next apply/set_state/close */
const double *hst_state(const hst_ctx *ctx);
The rest of the ABI
Eleven exported symbols total. The build fails if anything else is exported. hst_recompute_full is the honest baseline you measure against — it does the whole dense recompute and is deliberately not metered.
int32_t hst_batch(const hst_ctx *ctx);
int32_t hst_output_dim(const hst_ctx *ctx);
int32_t hst_input_dim(const hst_ctx *ctx);
int hst_set_state(hst_ctx *ctx,
const double *y0, int32_t len);
int hst_recompute_full(hst_ctx *ctx, double *y_out);
void hst_close(hst_ctx *ctx);
const char *hst_version(void);
Python binding
A ctypes wrapper over the same shared object ships in the package as hst_embedded.py. It is a thin binding, not a service client — it loads the library into the Python process. It binds the single-lane entry points; batched open is C and JVM only.
from hst_embedded import HstCore
with HstCore("op.bin", license_token) as ctx:
for cols, vals in stream:
y = ctx.apply_delta(cols, vals)
consume(y)
JVM binding
Java calls the same ABI through the JDK 22+ Foreign Function & Memory API — no JNI, no JNA, no native glue to compile. Package sci.horne.hstcore. This binding does cover batched open.
import sci.horne.hstcore.HstCore;
try (HstCore ctx =
HstCore.openBatched("op.bin", license, 16)) {
while (stream.hasNext()) {
double[] y = ctx.applyDelta(cols, vals);
consume(y);
}
}
Licensing
A license is a signed token: base64(payload).base64(ed25519_signature), verified against a public key compiled into the library. The signing key is offline — the library can validate a token but cannot mint one. The payload carries a customer id, an expiry, maximum operator dimensions, and quotas.
cid customer id
exp expiry (unix seconds)
max_n max operator rows
max_m max operator cols
max_files artifact opens allowed
max_applies delta applies allowed
Metering, offline and fail-closed
Usage is counted locally — no phone-home, nothing to allowlist in your firewall. The counter lives under ~/.hst/usage/ (relocatable with HST_USAGE_DIR), sealed with authenticated encryption and written atomically, with high-water marks so a rollback of the file cannot restore consumed quota.
hst_open -> NULL, "file quota exhausted"
hst_apply_delta -> -3, apply quota exhausted
hst_recompute_full is never metered.
Evaluation
What you actually receive.
One tarball, hstcore-embedded-<version>-<os>-<arch>.tar.gz, built for your platform. No installer, no container, no account to create.
lib/libhstcore.so # or .dylib
include/hstcore.h
bin/hst_compare # before/after harness
share/sample_op.bin
share/sample_stream.bin
share/STREAM_FORMAT.md
example.c
hst_embedded.py
eval.license
README.md QUICKSTART.md SECURITY.md
hst_compare is the point of the package. It replays a delta stream through both paths — full recompute and delta apply — asserts the two outputs match exactly, and prints the speedup and the quota it consumed. Run it against the sample first, then against your own artifact and your own stream:
./bin/hst_compare --license "$(cat eval.license)"
./bin/hst_compare --license "$(cat eval.license)" \
--op your_op.bin \
--stream your_stream.bin \
--dump out.tsv
The evaluation license is dimension-bounded and quota-bounded, and expires. If your operator is larger than the evaluation bounds, or you need more applies than the evaluation allows, that is a conversation rather than a workaround — get in touch. If you have not run the fit test yet, start there; it is faster to rule a workload out in six questions than in a build.