hearth-engine
Deterministic model residency, in Python. Keep declared models warm, and tell the truth about which ones are.
Native bindings to hearth's Rust core, built with PyO3 and maturin. No GPU required to use this package — it is the decision procedure, not the runtime.
pip install hearth-engine
import hearth
One abi3 wheel per platform covers Python 3.9+, so a new Python release does not need a new wheel.
Why you would want this
Your inference call hangs, then fails. Three completely different things cause that, and they arrive looking identical:
- the runtime evicted the model to free VRAM,
- the host detached the GPU and gave it to another tenant,
- a 32B model was simply still loading.
One is a capacity problem you own. One is your provider's, and no configuration you write will touch it. One is not a problem at all. A timeout cannot tell you which — so all three get "fixed" repeatedly and none of them go away.
Will this card hold this roster?
Answered before anything loads.
from hearth import GIB, declare, plan
p = plan(48 * GIB, [
declare("muse-local:latest", 20 * GIB, GIB),
declare("deepseek-r1:32b", 20 * GIB, GIB),
declare("gemma4:26b", 16 * GIB, GIB),
])
print(p["explain"])
# 2 of 3 admitted, 42.0 GiB committed of 44.2 GiB usable
# REJECTED gemma4:26b — needs 17.0 GiB, 2.2 GiB free, short by 14.8 GiB
for r in p["rejected"]:
print(f"{r['model']}: short by {r['short_bytes'] / GIB:.1f} GiB")
# gemma4:26b: short by 14.8 GiB
Declare five 20 GiB models on a 48 GiB card and no runtime will error. It loads, evicts, loads, evicts, forever, and presents to everyone as "the models got slow." This refuses the model that does not fit and tells you the shortfall.
Two rules in the planner are deliberate:
- Declaration order is priority order. First fit, never best fit — reordering to squeeze in one more model would silently demote whatever you listed first, and on a serving box first means most important.
- The reserve is never planned into (default 8%). Weights are not the whole cost: KV cache grows with context and parallelism, each CUDA context is hundreds of megabytes, and fragmentation is real on a card that has been up for weeks.
A live fleet, and an answer you can act on
from hearth import GIB, Fleet, declare
fleet = Fleet(48 * GIB, [declare("muse-local:latest", 20 * GIB, GIB)])
fleet.set_endpoint("muse-local:latest", "127.0.0.1:8090")
fleet.observe("muse-local:latest", "load_started")
r = fleet.route("muse-local:latest")
if r["ready"]:
send_to(r["endpoint"])
elif r["try_elsewhere"]:
retry_elsewhere(score_down=r["operator_fault"])
route() gives you three booleans answering three different questions:
| field | question |
|---|---|
ready |
can I send this request here, right now |
try_elsewhere |
should I go find another node |
operator_fault |
is this the operator's fault — False for a detached GPU |
That last one is the one nothing else reports. A reputation system fed the wrong answer slowly deletes its own honest operators.
The route key names which case you are in:
{"route": "unknown", "ready": False, "try_elsewhere": True, "operator_fault": False}
{"route": "warming", "for_ms": 20000, "ready": False, "try_elsewhere": False}
{"route": "ready", "endpoint": "127.0.0.1:8090", "ready": True}
{"route": "lost", "reason": "gpu_detached", "operator_fault": False, "try_elsewhere": True}
{"route": "lost", "reason": "evicted", "operator_fault": True, "try_elsewhere": True}
{"route": "not_admitted", "short_bytes": 19155554136, "try_elsewhere": True}
{"route": "not_declared", "try_elsewhere": True}
warming is the one every stack gets wrong: wait or route around, but do not fault this node. Routing to a model that is still coming up, then calling the inevitable timeout an error, is the most common way a serving stack lies about itself.
Key naming: every key this package returns is
snake_case, and every key it accepts is too. The Node package returns camelCase for the same data — each is idiomatic for its own language, so do not copy key names between the two.
Recording what you observed
fleet.observe(model, kind, detail=None, now=None)
kind is one of load_started · probe_ok · probe_failed · process_exited · load_failed · stop. now defaults to now_ms().
The single most important field you will ever pass here is gpu_present on a probe_failed:
fleet.observe("muse-local:latest", "probe_failed", {
"gpu_present": False, # the card is GONE — not this operator's fault
"detail": "no CUDA device",
})
Omit it and it reads as True — "the card was still there" — so a missing field can never quietly exonerate an operator. Absence has to be positively observed.
Either spelling works —
gpu_presentorgpuPresent, andvram_bytesorvramBytesonprobe_ok. Up to and including 0.3.2 this parser read only the snake_case spelling and an unrecognised key fell back to "the GPU was present", so{"gpuPresent": False}reportedevictedwithoperator_fault: True— the opposite of what the caller meant, silently. The mapping now lives inhearth-coreand is tested once, so the two bindings cannot drift apart again.
You pass facts, never conclusions. What state those produce is the core's job, decided by one state machine tested once in Rust rather than three times in three languages.
Everything, as one block
print(fleet.report())
# 0.0 / 44.2 GiB held (1 declared, 1 admitted)
# muse-local:latest loading for 5s
Same text hearth status prints — one truth in two places is how they stop matching.
Run the example
python examples/python/the_night.py
Replays the night hearth was built for: a model warms up, serves for an hour, the host takes the card away — and the router is told, in words, that it was not the operator's fault.
Also available
| package | registry |
|---|---|
@interchained/hearth |
npm |
hearth-core |
crates.io — the pure logic |
hearth-serve |
crates.io — the supervisor and hearth CLI |
Built by Vex × Interchained
© Interchained LLC · BUSL-1.1 (converts to Apache-2.0 on 2030-08-27)
Metadata
Release files for hearth-engine 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| hearth_engine-0.5.0.tar.gz | 59.6 kB | Without provenance | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| hearth_engine-0.5.0-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| hearth_engine-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| hearth_engine-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| hearth_engine-0.5.0-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| hearth_engine-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 1.6 MB
Release files / hearth_engine-0.5.0.tar.gz
| Download URL | hearth_engine-0.5.0.tar.gz |
|---|---|
| Size | 59.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
71f41bf84f0932c3de4d8e708682854f3190d4d58e81bc24b42dc5696157a7b7
|
|
BLAKE2b-256 checksum How to use checksums |
d4c61ff28c6521a73bf9c3f17f0789fcb274c10578a7b5e2b0c19255fb5f70f8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / hearth_engine-0.5.0-cp39-abi3-win_amd64.whl
| Download URL | hearth_engine-0.5.0-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 205.9 kB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
c5decebce0d28136f1854f64b8d38f5970e132ad33fb8a4a486f002d716df6b7
|
|
BLAKE2b-256 checksum How to use checksums |
731ef40e368a705b34834e124b543a4edb5f872455c0ca9e74d424bbd95b76c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / hearth_engine-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | hearth_engine-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 351.3 kB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
92d53ce4f847bc1a46fd51b076e4f8373bcab9d34f4db9c14a6d19eadf412bae
|
|
BLAKE2b-256 checksum How to use checksums |
3107e3dbfe62add8656f2a64b4d3cd94a510c40f5a5f459baa4637157062f9ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / hearth_engine-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | hearth_engine-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 339.5 kB |
| Tags | CPython 3.9 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
8c3625afa3e86c84237ddfa9ed7c838522142ec1b82d306159d97ef955a6c260
|
|
BLAKE2b-256 checksum How to use checksums |
5a2f8b8fc6a3a05b2c91fb43d177e45d200d746c5b09045e6254aa7d469c4d13
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / hearth_engine-0.5.0-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | hearth_engine-0.5.0-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 299.8 kB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
bb6fc54af42a1be0c607ebcb168dfb7d4e995a0cbdfe974ac79a0161712ee944
|
|
BLAKE2b-256 checksum How to use checksums |
0b48db4b5227085ae06b76b3cb4b0b9662209941bf403da8f77cef68e5a6ed80
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / hearth_engine-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | hearth_engine-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 307.3 kB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
20194b4a433e394d62e201253af9ddca7b3b033d464253de9230f94fd2a002e9
|
|
BLAKE2b-256 checksum How to use checksums |
133d5d238bdd0618abb04f476f74ac0dfa130a8be996b3837b57f59e739f56ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Security for hearth-engine 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.