- pm.environment owns _RESOLVER_MARKERS: the streaming uv runner imported
pm.workspace, whose tomllib import fails on the 3.10 system python that
bootstraps the Docker arm64 image (No module named 'tomllib'). Invariant test
proves runtime staging needs neither tomllib nor pm.workspace.
- run_tests.sh forwards the MSVC/SDK/Rust/OpenSSL toolchain variables through
its env -i scrub so PM tests that compile ruamel-yaml-clib on Windows arm64
find cl.exe (previously 'Visual C++ 14.0 or greater is required').
- nix desktop-backend check: the spawned backend outlives cage's process group
and kept writing under the temp HERMES_HOME during rmtree; stop every
process bound to the throwaway HOME before cleanup.
- windows: test_launcher_runtime_selection imports runtime_state from
hermes_cli (moved in bbec973514); the ' spaced ' suffix row loses its
trailing space on win32 (the filesystem strips it).
- macOS: test_sealed_worker_command copies the interpreter into the payload
(the escape guard resolves symlinks) and links the host lib tree.
python-build-standalone ships share/terminfo/1/1178 -> ../a/adm1178. The
3.10.12 backport of the 'data' filter resolves a symlink target from the
archive root, not the link's directory, and rejects it, so PM could not
install Python on the ubuntu-22.04-arm docker runner. Resolve symlink targets
ourselves (from the link's own directory, still confined to the destination)
and keep the stdlib filter for everything else.
- voice_mode: `_import_audio` asks `pm.ensure_import("audio-io")` before importing
sounddevice, so `/voice on` turns the feature on (or reports exactly why PM
refused: lazy installs off, platform gate, restart needed) instead of printing a
`python -c "from pm import sync_venv…"` one-liner for the user to run.
- pm.plugins_state: `read_home_selection` and `dependency_homes` are the public
readers; memory_provider_migration and plugins_cmd stop importing underscored names.
- pm.store: the `sha256_file` shim is gone; the authority test asserts the Store has no
file hash of its own rather than patching one.
- tests/pm/conftest: `isolated_machine_home` is autouse (Path.home, HOME, USERPROFILE,
HERMES_HOME all under tmp_path); `@pytest.mark.real_machine_home` opts a module out
— the four suites that spawn children with a home they build themselves.
- activate / activate.ps1 / Dockerfile followed hermes_cli.runtime_paths to
pm.environments (the earlier sweep only covered .py).
- pm.registry loads builtins through importlib so a test patching `pm.packages` in
sys.modules still registers the security packages.
`import pm.ensure` bound the submodule onto the package, shadowing the facade's
`pm.ensure()` function for every later caller in the process (photon's sidecar
start hit `'module' object is not callable`). The module is pm.install now; the
function keeps its name. The facade resolves through `__import__` rather than
`importlib.import_module` so a test that patches import_module globally does not
break attribute access on pm.
tools/lazy_deps.py returns to the 16-line stop_for_relaunch shim the branch wrote
(an origin/main merge had replaced it with main's 775-line implementation); the
project-metadata tests follow. update_cmd re-exports the four old_updater_deps
names the shim tests resolve through hermes_cli.update_cmd.
post_update._install_root, boot_bootstrap.default_project_root,
update_channel._default_root, version_info._CODE_ROOT + _resolve_stamp_file,
_launchers.publish_launchers and pm.runtime each re-derived "HERMES_INSTALL_ROOT
or the code root". pm.paths.install_root() is the one place now; pm.store reuses
the downloader's User-Agent instead of carrying a second one.
hermes_cli.runtime_paths (venv generations, selection, activation) moves to
pm.environments, and gains venv_bin_dir / venv_python / project_python. Every
in-tree caller asks pm for an interpreter now; pm no longer reaches back into
hermes_cli for its own environment layout (pm.packages, pm.extras, pm.ensure,
pm.paths imported hermes_cli.runtime_paths). The three open-coded
"Scripts/python.exe or bin/python" ladders in pm collapse onto venv_python.
hermes_constants.venv_python_path / venv_bin_dir and hermes_cli.runtime_paths
stay as frozen-updater-surface shims only (tests/compat/old_updater_surface.json).
To keep the boot path light, pm/__init__ resolves its facade lazily (PEP 562)
and pm.registry loads the built-in package definitions on first read instead of
at import: `import hermes_bootstrap` now loads pm + pm.environments only (25ms,
was 37ms with the eager facade dragging in the downloader). The stripped-payload
fixtures that ship only pre-import files keep working for the same reason.
Also restores two frozen-surface re-exports the F401 sweep dropped
(banner._github_compare_behind, cua_backend.resolve_cua_driver_cmd).
- 15 `MERGE-CHECK:` conflict-resolution comments removed from prod code (two were
TODOs already done: the utf-8-sig sessions.json read lives in session_persistence,
the pm-aware cron script helpers in scheduler_script).
- 49 imports the branch left unused (ruff F401, none present at the merge base,
none inside PLUGIN-COMPAT blocks). update_cmd's frozen-surface re-exports are
trimmed to the names tests/compat/old_updater_surface.json actually lists under
hermes_cli.update_cmd; the rest resolve through hermes_cli.main.__getattr__.
- tools/environments/local_gitbash_probe.py: nothing imported it once _find_bash
delegated to pm.shell().
- Three try/except wrappers around calls that cannot raise (install_truststore,
get_hermes_home, and a duplicated except clause in supermemory).
Reconcile plugin declarations and validation through PM's atomic generation publication; preserve external runtimes, target markers, and conflict refusal. Keep one source-update completion owner and port upstream lifecycle changes to the PM desktop/runtime paths.
martin-riedl's Linux build is compiled without x11grab, so screen capture
on X11 cannot work with the ffmpeg we ship. Verified on the pinned
artifact: the 9.0.1 amd64 binary contains no 'x11grab'/'xcbgrab' symbol at
all, while BtbN's linux64-gpl build carries it.
Linux now resolves through the same BtbN/FFmpeg-Builds stream as Windows
(btbn_index() learned the linux64/linuxarm64 assets; Linux ships .tar.xz
where Windows ships .zip, hence the archive-extension split in the
binary_rel table). macOS keeps martin-riedl and the bionic target keeps
its termux .deb.
Lockfile: both Linux rows repinned to the autobuild tag the Windows rows
already use, so one upstream snapshot covers win+linux. Hashes are from
the real downloads.
Tests: the three fixtures that encoded 'linux -> martin-riedl' now encode
the source split instead.
The termux-main pool deletes a package's previous archive when it rebuilds, so
the runtime-lib pin table and the bionic lock rows rot without warning. The last
rotation broke a build on eight rows at once, and the stager's concurrent
downloads only surfaced whichever 404 won the race.
pm now owns the pin table it repairs: scripts/termux/runtime_libs.json moves to
pm/termux_runtime_libs.json, so pins live in pm/ and scripts consume them — the
direction scripts/ci/archive_inputs.py already reads pm/lock.json in.
`hermes pm update --termux` repins exactly the rows whose archive the pool has
replaced, hashing each replacement against the index SHA256 before writing
url/version/hash together. `--check` reports without writing and exits 1, so a
retired pin can fail a cheap preflight instead of a payload build.
It is a repair, not an update: an alive pin is never moved, because a repin can
land a rebuilt library under a moved soname and the table is the payload's
recursive DT_NEEDED closure. A pin whose package the pool has dropped outright
is reported and left alone. `--termux` runs alone — names/--target/--uv/--npm
are ignored, since repairing foreign-target pins is not a version resolution.
Verified: `pm update --termux --check` against the live pool reports 89 rows
served; a table deliberately pinned to the retired libiconv 1.18-1 repins to
1.19 with the pool's hash through the real network path; 19 new tests; the
tests/pm, tests/ci and tests/scripts suites have the same failure set as the
base commit (91 pre-existing Windows environment failures, none new).
hermes pm install (and activate/activate.ps1 behind it) now updates a
single live progress line instead of printing one line per ~4 MiB tick.
Detected via isatty(); stderr is the fallback because activate.ps1 pipes
stdout through Out-Host while stderr stays on the console. Off a TTY the
output is unchanged (same throttle, one line per tick) so CI logs are
unaffected.
Facts.installed() read fact["entry"] unconditionally, but the venv state
fact (record_state: stamp + extras) shares facts.json with tool facts and
owns no store entry. env_for(*all_packages()) walks venv, so activate
(hermes_cli.runtime_paths) crashed with KeyError: 'entry' right after a
successful sync. A fact without an entry is not a store install.
The CI cache barely ever restored anything useful, for four independent
reasons found in live run logs and the fork's cache store:
- `uv cache prune --ci` (setup-pm/prune) discards downloaded wheels before
the save, so the snapshot carried only source-built wheels — the next
run "restored" it and still cold-downloaded everything. Upstream main's
logs showed the end state: a 209KB stub cache exact-hitting forever.
- Tool-only jobs (icons-freshness etc.) auto-saved a 1.5KB empty uv cache
under the production exact key; caches are immutable, so the stub won
forever and blocked real saves.
- The npm cache key had no restore-keys, so one lockfile bump missed the
exact key and every npm job went cold.
- `2efa4ff94f` deleted the electron-builder toolchain save but left the
assemble job restoring `eb2-` — a fossil nobody regenerates; assembly
cold-downloads winCodeSign/ATS/dotnet on every release.
Changes:
- pm/cache_lock.py: move prune_uv_cache_to_lock out of
scripts/bundles/native.py (which re-exports it); the lock-exactness
contract now serves both the bundle ship gate and CI caches.
- pm.build_env learns --exact-lock --lock-source: prune cache entries the
project uv.lock cannot resolve, keeping lock-required downloaded wheels
(unlike --ci). Refuses --ci/--prune-cache combinations.
- setup-pm: python-cache auto-save now requires extras (no stubs from
tool-only jobs); key drops the prune flag and bumps to v3 — pruned and
unpruned saves share one namespace since both are lock-exact; pre-save
pruning switched from --ci to --exact-lock; npm cache gains a
lockfile-agnostic restore prefix.
- save-pm-cache: same exact-lock prune before explicit saves.
- desktop-bundled-release: build legs (cache-mode: write) restore+save the
electron-builder toolchain under eb3- keyed on the locked builder
version; assemble restores the same namespace; the dead default-cache
resolution step is removed (assembly resolves no electron artifacts).
- cleanup_pm_toolchain_caches.py: match v2 and v3 smoke keys.
Validation: tests/scripts/test_bundle_native.py 11/11 (incl. both
lock-prune gates), tests/pm failures identical before/after the diff,
tests/scripts/test_bundle_payload.py 5/5, tests/ci cleanup 3/3,
tests-js setup-pm-post 1/1 and the three setup-pm-cache contract tests
updated and green (6 failures in that file pre-date this diff and are
drift between the workflow and its stale assertions); pm.build_env
--exact-lock E2E against a real uv cache copy pruned 3 stale entries and
kept the rest.
pm.adopt() re-hashed every staged entry and cross-checked shipped facts
against the shipped lock at first boot of a bundled install. The bundle
payload is already proven by the signed artifact at download time; the
first-boot re-hash overlapped that guarantee (and per-boot drift()
stamps) while facts/lock coherence is downstream of CI building the
right bundle in the first place. Defense-in-depth with no incremental
threat model; startup now goes straight to pm.activate().
Drops pm.adopt, its export, the venv_sync check_runtime call, and the
adopt tests + _bundle_payload fixture.
Three ways a fresh install punished a second backend:
- `runtime_lock` waited forever, and its holder can be rebuilding the whole dependency
environment. `activate_dependencies` runs at every boot, so the second backend — the
onboarding profile — never bound. It now yields whether it holds the lock, and boot
proceeds without it: recovery is the holder's job, and the generation being leased is the
selected one, which the collector never removes. Explicit installs still pass
`timeout=None`; an opportunistic lazy install refuses instead of queueing behind a rebuild
it did not request. Same rule `boot_bootstrap._RecordLock` already states for home
maintenance.
- `stt-whisper` had no platform gate although its anchor `faster_whisper` cannot install on
win32/ARM64 (ctranslate2 ships no wheel) or darwin-x64, so the lazy install rebuilt the
whole environment and still failed the anchor on every retry. The extra is gated to match
its dependency markers; `voice` stays ungated because its sounddevice/numpy do install on
those targets.
- the first sync copies the payload's uv cache out to the machine cache. A copy that failed
still recorded `.seeded`, so the partial seed was permanent and every later offline sync
failed closed on the missing entries.
Validated: tests/pm/ and tests/hermes_cli/ for the touched modules. A/B on HEAD: the gate
test, the cache-seed test and both lock tests fail there, pass here.
Dependency acquisition during packaging left native wheels and packager
inputs outside the pre-build cache save. Compose PM and existing providers
into a preparation phase, then require builds to consume admitted inputs.
Share native preparation with PM Bundle. Keep path-bound environments and
signing outputs separate from reusable caches. Use read-only cache tokens
for commit builds and preserve the one-command local build path.
Verify pinned tools through PM, probe PTYs under the prepared Electron,
and supply dmgbuild through a build-only PM package. Resolve bundled tool
stores from their payload manifest so relocation preserves discovery.
Validation: focused Python and JS tests, checkJs, Ruff, Windows checks,
anti-slop, cache relocation, and network-denied Linux AppImage builds.
Relocated runtime smoke passed with NixOS host libraries supplied.
Native Windows/macOS signing and live GitHub cache behavior remain untested.
Limit lock-held verification reuse to the batch installer that benefits
from it. A standalone explicit ensure has no repeated closure entries and
must not wait for an unrelated long download before verifying a warm tool.
A real store-lock regression timed out before the fix and now completes
while the writer retains its lock. Batch verification, corruption repair,
worker progress and download-control coverage remain green: 84 passed,
one native-platform skip, retries disabled.
Cross-target Node verification attempts to execute foreign bytes before
and after publication. Check the native target before smoke probes, while
retaining file and architecture checks for every target.
Repair must retain a plugin's build directory when it contains the declared
PEP 517 backend. Use the same copy exclusions as the initial snapshot.
The foreign-ELF execution trap and offline real-uv replay test fail before
the fixes and pass after them. Native smoke probes and bionic no-execution
checks also pass. The focused PM run reports nine unrelated failures,
all reproduced at the starting commit. No full suite or native Windows
validation was run.
Use one acquisition, assembly, verification, replacement, rollback and cleanup path. Keep host facts and cross-target markers as concrete recording differences, including interrupted-entry recovery and verified Python copies.
Exercise both routes with real archive servers, pause/resume, multi-archive progress, post-publication failure and killed publishers. Keep native Windows directory-hold coverage gated to its host.
Keep desktop rollback in staged publication instead of restoring backups
inside a candidate directory that will be discarded.
Use the npm tag endpoint, let the downloader own archive verification,
and ensure CI toolchain roots without repeating dependency verification.
Remove an unused resolver input and replace overlapping tests with
fault-proven lifecycle coverage.
Verified with the scoped Python gate and before-pack tests. Native
Windows/macOS update execution and the full suite were not run.
Run historical updater completion in a fresh interpreter so cached imports
cannot revive retired dependency installers. Share Git and ZIP completion,
carry receipt and recovery state, and preserve child exit status.
Route plugin admission, binary acquisition, desktop launch and build paths
through PM. Replace redundant helpers and tests with real worker, package,
publication and launch checks. Keep the shipped compatibility surface fixed.
Targeted Python and desktop checks pass. Native update journeys and fresh
production image qualification remain pending. This is a checkpoint before
those acceptance runs.
Enable verbose uv output whenever PM streams a command so long native dependency builds expose package activity and backend stdout/stderr before failure.
Verify both sync and requirements installs with real offline uv builds that wait for their output to reach the parent. Both cases fail on the base and pass with this change.
Competing installers and checkout-local venv assumptions bypassed PM
selection, install consent, and generation lifetimes. Route consumers
through PM and installation-bound launchers. Refresh source launchers
before obsolete Python entries can be collected.
Remove Node, browser, and CUA acquisition engines, obsolete venv-holder
handling, detached sync, and unused PM APIs. Keep historical updater
exports inert and preserve external tool ownership and native integration.
Share product freshness and prepared inputs across builders. Align plugin
admission, Docker provisioning, setup instructions, and behavioral tests.
Verified targeted Python and JavaScript tests, desktop and web typechecks,
scoped lint, real product builds, and the Docker frontend smoke test.
The missed post-setup test cleanup is included and verified.
Native Windows/macOS execution, full Rust compilation, and the complete
repository suite remain unverified. Historical compatibility requirements
were preserved and extended, not fully rescanned.
Resolve the default cache before isolating HOME so payload builds use the directory that CI restores and saves. Remove the standalone bundle workflow dependency on an unset cache variable.
Verified offline wheel reuse in isolated children, 20 focused tests, Ruff, and actionlint. Full native release builds and the separate source-build timeout remain unverified.
Python 3.11 fails while PM records the replacement interpreter's tree digest. The bootstrap cannot require Path.is_junction before it installs the managed Python.
Use Windows reparse metadata for junction detection in the store and data cleanup. Retain junction target protection without requiring the newer pathlib API.
Verified the exact failure on real Python 3.11 before the fix. Cold provisioning and source-update relaunch pass afterward. Targeted tests: 16 passed, one Windows-only test skipped on Linux. Ruff passed. The full suite and native Windows test were not run.
Ready tools do not authorize dependency operations when lazy installs are
disabled. Apply the shared guard before build, export, or online lock checks.
Keep offline lock checks passive. Cache pruning only reads the pinned
toolchain and cannot acquire missing tools.
Use one requirements-file installer for requirement builds and runtime
wheelhouse staging. Both enforce the same index and binary-only policy.
Verified 36 targeted tests, including missing-toolchain pruning, disabled
lazy operations with ready tools, and real offline runtime staging.
Build callers need lock validation without mutation and frozen exports that
retain markers and Git pins. Route these operations through the private
engine instead of giving callers uv commands.
Requirement builds claim fresh destinations, validate installed packages,
and remove failed candidates. Wheelhouse builds reject indexes and source
builds. Cache pruning keeps downloaded wheels except in CI mode.
Verified with real local wheels, a local Git source, and loopback downloads:
30 targeted tests passed across test_build_operations.py and
test_environment_build.py. No full-suite claim while the base migration is
in progress.
Setup needs declared extras instead of unbounded package installs. Pin ddgs,
langfuse and Piper without changing any existing resolved package versions.
Use upload-time cutoffs for the reviewed pins.
NeuTTS excludes Python 3.14. KittenTTS requires misaki, which excludes Python
3.13 and newer. Keep matching dependency markers and PM gates so bundles omit
these engines and explicit requests fail instead of reporting empty success.
Piper also excludes Intel macOS and Windows ARM64 because its native closure
lacks wheels there.
Verify the parent sync gate against the native Python version, including an
installed-anchor override. The test fails without that gate. Check declaration
and runtime gate agreement across bundle targets. Isolate an existing test's
home lookup, whose failure also reproduces on the base commit.
Verification: public PM lock/check and fresh 85-package environment passed in
an isolated manager runtime. DDGS, Langfuse and Piper imports passed on Linux.
No full suite or cross-host execution was performed.
The minted launchers wired venv site-packages onto sys.path with a raw
insert (win32 wrapper) / PYTHONPATH (posix), neither of which runs .pth
files. pywin32.pth is load-bearing on Windows: it puts win32\lib on
sys.path, which is what makes 'import pywintypes' resolve — without it
portalocker's Win32Locker dies and concurrent-log-handler silently drops
every file-log record on Windows bundles.
* launcher_wrapper.py: site.addsitedir() for the site entry (repo first,
site directly after, .pth dirs last)
* launchers.py posix: same via HERMES_SITE env in the -c bootstrap
* pm/environment.py: prune_site_pth() drops _virtualenv.pth and the
__editable__ pointer (build-machine path) that must never run in a
sealed payload
* python_env.py: run the prune after every environment build
The fixed openWakeWord default selects an unavailable engine on native
Windows ARM64 and Intel macOS. Use auto and the existing PM platform gates
to prefer openWakeWord, then sherpa, then Porcupine.
Keep explicit provider choices unchanged. Porcupine still requires its
access key, and wake detection remains disabled until the user enables it.
Expose auto in the config UI and document the backend-platform selection.
Verified config loading, platform selection, explicit-provider preservation,
key requirements, and the config schema. No microphone detection was run.