Proposal: fully offline hk via an embedded pkl package (seed the persistent cache) #1216
Replies: 1 comment 4 replies
|
Thanks for the detailed proposal. I think the release-asset plumbing is manageable: the asset name, URL, and contents are predictable from the hk version, so we should be able to generate or stage the matching archive as part of the build. My main hesitation is the exact-version coupling. The embedded package only provides a cold-start offline path when That said, the size and runtime cost look small, and mismatched versions can retain the current behavior. I would be open to reviewing a PR. It should preserve existing cache/custom rewrite precedence, fall back normally on version mismatch, and test both a matching cold offline start and the mismatch case. AI-assisted — Tool: Codex; model: openai/gpt-5; version: unavailable. |
Uh oh!
There was an error while loading. Please reload this page.
Context
This follows up on discussion #1198 ("How do I run hk entirely offline"), where two working options were laid out:
hk@X.ziponce while online, extract to.hk/pkl/hk-X/, rewrite the two imports inhk.pklto local paths, and commit. Strongest today: a cold machine needs no network. Costs: per-repo setup, ~650 KB committed into every repo, imports diverge from the canonicalpackage://form, and it must be re-run on every version bump.HK_PKL_CACHE_DIR+HK_PKL_OFFLINE. Once a package is on disk, evaluation survives config-cache invalidation and works offline. Costs: still needs one online cold-warm, and the cache must survive between runs.Summary
Both options above leave the same gap: the cold start. A fresh environment that has never warmed the cache (option 2) or vendored the package (option 1) still has to fetch
https://github.com/jdx/hk/releases/download/vX/hk@X.zip(which 302-redirects torelease-assets.githubusercontent.com) on the very first evaluation — exactly what fails or is unavailable in restricted/corporate networks and on cold CI runners.This proposes a third option: embed the matching-version pkl package in the hk binary and seed the persistent cache from it before evaluation. Unlike vendoring, nothing is committed per-repo and imports stay in canonical form; unlike the persistent cache alone, no online cold-warm is needed. It is an extension of #1199, not a competing mechanism — the embedded copy simply pre-populates the cache #1199 already manages, for the common case where
hk.pklpins the running binary's version.Why the cache-seed approach fits pklr as-is
Reading pklr 1.4.0 (
src/eval.rs), the persistent cache is a flat, content-addressed store — much simpler than Apple pkl's~/.pkl/cachelayout:resolve_package_urimaps hk's config imports (Format 3) to a single zip URL:package://github.com/jdx/hk/releases/download/v1.56.0/hk@1.56.0#/Config.pkl→
PackageSource::Zip("https://github.com/jdx/hk/releases/download/v1.56.0/hk@1.56.0.zip", "Config.pkl")amends …#/Config.pklandimport …#/Builtins.pklresolve to the same zip — the exact release asset hk already builds viapkl project package pkland uploads.package_cache_paths(cache_dir, url, ext)stores two files under<cache_dir>/packages/:<fnv1a64(url):016x>.zip— the raw zip bytes<fnv1a64(url):016x>.url— the original URL (collision guard)validate_package_bytesfor a zip only checks that it opens and every entry reads, which the real artifact passes.fetch_package_bytesreads the cache first and, whenoffline, short-circuits before any network call. So a seeded cache +HK_PKL_OFFLINE=truemeans zero network, deterministically.So "embed the package" reduces to "write the embedded zip into the cache in pklr's format, keyed by the URL hk's own version resolves to."
Proposed change: two small, coordinated PRs
PR 1 — pklr: expose a
preload_packagemethodReuse the two functions that already exist (
resolve_package_uri,write_package_cache) so the cache layout and hash scheme stay private to pklr and callers never depend on them:(Exact signature/placement —
Evaluatormethod vs. also a builder passthrough — is yours to decide; this is the shape.)PR 2 — hk: embed the package and seed the cache
Before the evaluator runs (
src/config.rs,run_pklr), seed the cache for the binary's own version. No need to parsehk.pkl— unconditionally seeding the running binary's version is correct: if the config pins that version (the common case) it's a cache hit and never fetches; otherwise the entry is simply unused.Plus a
HK_PKL_EMBEDDEDenv toggle (default on) so it can be disabled, and docs/CHANGELOG.The build step (the only fiddly part)
Builtins.pklis generated and git-ignored (scripts/gen_builtins.py), and the embedded artifact is the built package zip, so the rawpkl/tree can't be embedded directly. A smallbuild.rswould stagehk@{version}.zipinto$OUT_DIR/hk-pkl.zip:cargo build.is_empty()check above).Scope / size
build.rs, plus docs. Net ~80–120 lines, dominated by build-time artifact plumbing rather than logic.Behavior and trade-offs
hk.pklpins the same version as the running binary. That is the common case (release tooling rewrites configs to match, and users pin to what they installed). A config pinning a different version still fetches that version normally. Embedding is a cold-start optimization, not a replacement for thepackage://model.HK_PKL_EMBEDDED=0).package://…/releases/download/vXsource of truth is unchanged, and non-matching versions resolve exactly as today.All reactions