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 faultFalse 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 worksgpu_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)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hearth_engine-0.3.6.tar.gz (49.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

hearth_engine-0.3.6-cp39-abi3-win_amd64.whl (205.8 kB view details)

Uploaded CPython 3.9+Windows x86-64

hearth_engine-0.3.6-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (351.1 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

hearth_engine-0.3.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (339.2 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

hearth_engine-0.3.6-cp39-abi3-macosx_11_0_arm64.whl (301.4 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

hearth_engine-0.3.6-cp39-abi3-macosx_10_12_x86_64.whl (309.3 kB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file hearth_engine-0.3.6.tar.gz.

File metadata

  • Download URL: hearth_engine-0.3.6.tar.gz
  • Upload date:
  • Size: 49.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.15.0

File hashes

Hashes for hearth_engine-0.3.6.tar.gz
Algorithm Hash digest
SHA256 96be2c6f093ae5e58ff13bb98ca800430081f4b029d78320edf66eb5143d3b57
MD5 34ae4d57ae55fc74aaa756a73c27d8fa
BLAKE2b-256 22f648c7b3a266913ef422b2fc1f5a33d74ed6a8ee04dd87345517bd4743ac6c

See more details on using hashes here.

File details

Details for the file hearth_engine-0.3.6-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for hearth_engine-0.3.6-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 eaf39cbb921b3a3683f5fb2df8eaf6601424c70059bac697192e193642b0ae7e
MD5 242b208beb487fbc925fdf4de31e1d95
BLAKE2b-256 9f05a6fbb4b71fcbb6d0eb2e81826f2f835199f253da7fd3017e34b9b2b06fa1

See more details on using hashes here.

File details

Details for the file hearth_engine-0.3.6-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for hearth_engine-0.3.6-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e1b6b2b350fbb7dd4f11200e497973c98ef8c99e1ff5a4fc7bb7a0b2ab07d1a5
MD5 9a2958f5a437a025b7ff876524d14304
BLAKE2b-256 64656d699317156889a8353eaae82595b5f6b4376abb8a1bd5d51750ba91300d

See more details on using hashes here.

File details

Details for the file hearth_engine-0.3.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for hearth_engine-0.3.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 20716abb496c158114d0115cf378fa7113dbf4b9492492706da39059a26abcf4
MD5 6dbde8acd7241fa72f158045c2e767f1
BLAKE2b-256 8890f55a989a3f6e7d40ee4401e65ee6de0ecd35d47193457b62da144e0b1b3a

See more details on using hashes here.

File details

Details for the file hearth_engine-0.3.6-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for hearth_engine-0.3.6-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c3eb99b2dc1e57a20a7aa0c5d8e7f0a45b7e22822f8bbd3546809db641916a8b
MD5 0c8f6b47fe636c35c524faefc9ef7f04
BLAKE2b-256 7c75836e6017284b70a9c835893a83b2643124c203ba1ef032cf5b03e72c3d6d

See more details on using hashes here.

File details

Details for the file hearth_engine-0.3.6-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for hearth_engine-0.3.6-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 4092c94d0821d72dd636831b338a3b764993f115d7d822644a2d7c0037baa878
MD5 6dea03748f23d3b1ab8e8edef175e972
BLAKE2b-256 6b7c96c569e617cb2cc256ec3d3621789028e17fcd4e33443f80de07b7bf8a78

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.7

6 files

0.4.6

6 files

0.4.5

6 files

0.4.3

6 files

0.4.2

6 files

0.4.1

6 files

0.4.0

6 files

0.3.9

6 files

0.3.8

6 files

0.3.7

6 files

This release

0.3.6 This release

6 files

0.3.5

6 files

0.3.4

6 files

0.3.3

6 files

0.3.2

6 files

0.3.1

6 files

0.3.0

6 files

0.2.0

6 files

0.1.2

6 files

0.1.1

6 files

0.1.0

2 files

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