zettelkasten.bundle_builder¶
zettelkasten.bundle_builder ¶
Background, process-isolated builder for per-project view bundles.
This is Stage C of the load re-architecture: it moves the expensive native work
(markdown parse + embedding + t-SNE + clustering) that produces a project's
:mod:zettelkasten.bundle OFF the web server and into a ProcessPoolExecutor.
Why a process pool and not a thread pool: kglite (a native extension with
process-global state) and the scientific stack (sklearn/numpy/BLAS) corrupt each
other's heap if run concurrently in the SAME process — the reason the dashboard
caps its own sync threadpool to a single token and serializes native ops under
embeddings._NATIVE_LOCK. Separate processes each have their OWN native heap, so
several bundle builds run truly in parallel across cores with no shared-heap risk,
and none of them touch the web server's heap at all. The web server stays a
lightweight reader: on a bundle miss it schedules a build here and keeps serving
its existing (live) response; the next poll, once the bundle lands, is an instant
signature-gated read.
The scheduler is idempotent and deduplicated (mirrors
_common.schedule_rewarm): a burst of polls for the same project spawns at most
one in-flight build, and the in-progress marker is cleared in a done callback
so a failed build is retryable by a later request rather than wedged.
Disable entirely with ZETTEL_BUNDLE_POOL=0 (the routes then just serve their
live path with no background warming). Worker count defaults to a small fraction
of the machine and is overridable with ZETTEL_BUNDLE_WORKERS.
schedule_bundle_build ¶
Idempotently schedule a background bundle build for project.
Returns True when a build was scheduled, False when one is already in
flight for this project (dedup), the pool is disabled/unavailable, or
scheduling failed. Never blocks and never runs native code on the caller's
thread — the actual build runs in the process pool.
Source code in zettelkasten/bundle_builder.py
build_in_progress ¶
True when a background bundle build for project is currently in flight.
enable_write_prewarm ¶
Turn write-time bundle pre-warm on (called by a long-lived server startup).
note_written ¶
Cheap per-write signal (fired from ZettelGraph.save_note/delete_note).
No-op unless write-time pre-warm was enabled AND the build pool is enabled. Records the touched box and (re)arms a debounce timer so a burst of writes coalesces into one rebuild per affected project once the boxes go quiet. Never blocks and never runs native code on the caller's thread.
Source code in zettelkasten/bundle_builder.py
shutdown ¶
Tear down the build pool (best-effort). Safe to call when never started.
Also DISABLES write-prewarm: a note_written that lands after (or racing)
teardown — a late write during app shutdown, or the in-process chat agent
finishing a save — would otherwise pass the _prewarm_enabled gate, arm a
fresh timer, and on fire re-create a ProcessPoolExecutor that nothing tears
down again (orphaned workers). Clearing the flag under the same lock closes
that window; a server that restarts calls :func:enable_write_prewarm again.