Skip to main content

hearth-engine

Deterministic model residency, in Python. Keep declared models warm, and tell the truth about which ones are.

PyPI

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_present or gpuPresent, and vram_bytes or vramBytes on probe_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} reported evicted with operator_fault: True — the opposite of what the caller meant, silently. The mapping now lives in hearth-core and 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)

Source distribution for hearth-engine 0.5.0
File Size Uploaded Provenance
hearth_engine-0.5.0.tar.gz 59.6 kB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for hearth-engine 0.5.0
File
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page