12 KiB
Offline worker adapter and backend dispatcher
src/worker.hpp provides frameyap::Worker: call start(python, script, model, threads=2, advanced_debug=false) explicitly for the legacy Redux script, or
supply the optional backend, manifest_dir, root arguments for the manifest
dispatcher. Poll until ready(), then submit(id, pcm) and poll for one
WorkerReply (text or privacy-safe per-request error). One request at a time;
no queue, no capture and no input injection. stop() discards pending audio,
terminates/reaps its direct child (TERM, bounded 500 ms, then KILL),
and kills remaining processes in its dedicated owned worker group,
and is safe to repeat. Destruction stops it. start() returns without waiting
for model load; poll() reports warmup/load/crash/timeout/protocol errors by
throwing, then stops. Launch uses posix_spawn, safe with the host's OpenVR/SDL
threads, rather than running Python setup in a forked multithreaded child.
Caller must discard stale authorization/results after
cancellation; this component does not implement focus or delivery policy.
The adapter requires an existing absolute, owner-private $XDG_RUNTIME_DIR
(no symlink at the final component), creates its own 0700 mkdtemp directory,
and writes only clip.raw with O_EXCL|O_NOFOLLOW, mode 0600. Clips are
3200..320000 finite float samples in [-1, 1], mono 16 kHz, stored as
little-endian IEEE float32 (0.2..20 s). The microphone adapter saturates finite
capture/resampler overshoot to this range without rescaling the rest of the
clip; NaN/infinity are rejected. Worker::submit independently rejects samples
outside this contract before writing a clip or request. Files are unlinked after replies or shutdown, and the
private directory is removed. Private clips are not encrypted against the
account owner/root; do not use an untrusted runtime directory. The caller
should pass a trusted interpreter and script. By default audio/transcripts are
not logged and child stderr is redirected to /dev/null. Request errors report
only a fixed stage (audio, inference, response) and built-in exception
category, never the exception message, arbitrary class name, traceback or text.
The native receiver allowlists those labels before displaying/logging them;
unknown/legacy errors remain generic.
The private pipes use unsigned LE32 payload lengths (1..65536), a one-byte
message type and, for requests/replies, unsigned LE64 request ID. T + ID
requests reading the fixed clip; Y means ready; F means load failure
(M for missing/mismatched pinned model or private clip directory, I for a
missing Python dependency, P for unavailable telemetry protection, D for runtime/model load failure); R + ID + UTF-8
text and E + ID + privacy-safe UTF-8 error are replies. Text is at most 4096
bytes. An unexpected or duplicate reply, wrong ID, extra frame, closed pipe
or oversized frame stops the worker. A correlated E reply or an invalid
transcript is a request-level failure: the Controller drops that clip,
keeps the ready model loaded and allows Record to retry. A broken protocol,
worker crash or explicit in-flight cancellation is different and can require a
reload. Warmup deadline is 120 s, transcription deadline 60 s; poll() must
be called regularly to enforce deadlines. It never initializes a headset or
starts a recording. There is no auto restart after a process failure.
Parakeet Redux is the model (moondream/parakeet-redux); moondream is
its Python inference package, not a second model or cloud endpoint. Kestrel is
a native dependency of that local runtime. python/frameyap/worker.py lazily
imports moondream only after explicit CLI startup, with HF/Transformers/Datasets
offline variables and bounded native thread-pool variables set before import. It uses
md.photon("moondream/parakeet-redux", model_path=<absolute local directory>, device="cpu", cpu_threads=threads) and persistent
transcribe(audio=<numpy float32>, sample_rate=16000)["text"].
Install an isolated Python runtime with the
moondream 2.4.0, kestrel 0.8.0 and compatible CPU dependencies; provide
preinstalled local weights from revision
2bf128600aac4b16946f7ed8372e56117fe5e23b and pass its directory
explicitly. The code verifies exact sizes and SHA-256 of weights/config/tokenizer
against assets/backends/redux.json via the shared, offline
python/frameyap/model_files.py schema/verifier before importing model libraries.
frameyap --list-models and frameyap --check-model redux --model-dir /absolute/model
(or scripts/model-status.py) read local manifests and optionally hash local
files; they never load the worker or download weights. For native --run,
--backend ID, --model-store /absolute/store, and
--manifest-dir /absolute/manifests are wired through the installed launcher
as explicit overrides; default selection is saved in config.json (redux). The dispatcher
python/frameyap/backend_worker.py validates a selected manifest and hashes
its local model, resolves an in-release relative Python/executable launcher
without a shell, then supervises one child over frameyap-worker-v1 (ready,
correlated request/reply and failure frames). The launcher must accept
{model_dir} and {clip_dir} (optional {threads}); matching manifest and
pinned weights are not a runtime or automatically trusted new backend.
Only Redux is supplied today. Protocol stdout is isolated at the file-descriptor
level from third-party diagnostics. Thread limits cover Torch interop/native
pools and CUDA is not selected. Offline environment
flags do not prove every third-party internal is unable to access a network.
Runtime/build/tests perform no downloads; the separate explicit setup utility
scripts/fetch-model.py can provision the public pinned weights.
Native-only archives do not bundle the runtime; the explicit
install.sh --install-runtime --yes step pip-installs it into a user-local venv
(see packaging and third-party notes). No public runtime bundle has been released.
Limited ARM64 measurements were taken during development; they are not a claim
of complete headset acceptance.
Moondream analytics
The persistent moondream_analytics boolean defaults to false, including when
absent from existing config. Settings requires a rendered disclosure and Confirm
Enable before saving true; failed enabling saves leave it off. Disabling takes
effect in-session even if saving fails, with an explicit next-launch warning and
retry control. The native controller passes the effective setting before its first
warmup and every restart. It overwrites inherited FRAMEYAP_MOONDREAM_ANALYTICS;
the manifest dispatcher forwards opt-in only to the known Redux Python launcher.
Direct worker invocation treats only environment value 1 as enabled; missing
or malformed values are off. Manual config edits are read on application restart.
With analytics off, the worker verifies moondream 2.4.0 and Kestrel 0.8.0 and the
private reporter coroutine interface, then replaces PhotonReporter._flush_window
with an async no-op before engine construction. Startup, periodic, shutdown and
nonrotating authentication-retry uploads share that method. It returns no account
standing, disabling cloud finetune authentication; FrameYap only uses the local
base Redux model. Incompatible versions/interfaces emit F/P and do not start
inference. Missing packages retain the existing F/I error. Enabled analytics
leave upstream behavior untouched, including its allowlisted API-base override
(https://api-staging.moondream.ai) and use of an inherited MOONDREAM_API_KEY
authentication header if present. FrameYap's local model needs no API key.
No installed package files are changed.
OFF persistence is deferred until after the old worker is stopped; a slow or failed config write cannot keep the old enabled reporter running. Enabling requires successful write, flush, close and rename before becoming effective.
Changing analytics cancels audio/review/queued delivery and immediately kills the owned worker process group before closing its pipes, without graceful telemetry hooks. The direct worker is reaped before replacement starts with the new choice. Already sent/in-flight network requests cannot be recalled. Normal shutdown still uses the existing bounded TERM/KILL cleanup. This is worker-scoped protection, not an OS network sandbox or a guarantee about all native/transitive dependencies.
Audit source: Kestrel 0.8.0 wheel
kestrel/photon.py, kestrel/engine/core.py, and
moondream 2.4.0 moondream/photon_vl.py.
The Kestrel wheel SHA-256 is
15c65bf12983d347330082838d4c6a9678a7f00c0f32ae089d9ccad7482e62bd.
Re-audit before changing the allowed versions: metadata/interface checks do not
detect a locally modified package retaining its original version.
Default offline tests cover config migration/persistence/failures, rendered opt-in consent and cancellation, initial worker policy, toggles during pending work, restart/cleanup, and version/interface failures. An optional exact-wheel test executes only the upstream reporter with fake HTTP/runtime modules, checking startup, idle periodic, shutdown and authentication retries in both states. It never uploads, loads a model, or downloads dependencies:
FRAMEYAP_TEST_KESTREL_WHEEL=/path/to/kestrel-0.8.0-py3-none-any.whl \
python3 -m unittest discover -s tests -p test_telemetry.py -v
Advanced debugging
Explicitly set "advanced_debug": true in config or enable Settings → Advanced
debugging. It is off by default. A Settings change restarts the owned worker,
closes the microphone and discards current work/review; the replacement worker
warms normally. Turning it off stops detailed capture but does not delete
previous logs. Manual config edits take effect on application restart.
With this opt-in the worker receives --advanced-debug: native/model stdout and
stderr, sample counts, full exception tracebacks and recognized transcripts are
captured. These logs may contain private speech, transcript text and local
paths. Inspect/redact before sharing; keep out of Git. No raw audio archive is
created; normal temporary clips still expire after replies/cancellation.
Files: $XDG_STATE_HOME/frameyap/worker-debug.log (fallback
~/.local/state/frameyap/worker-debug.log) and worker-debug.previous.log.
Each debug worker start rotates the current log once; only these two files are
retained, each bounded to 4 MiB. At the limit a marker is written and further
output is drained/discarded for that worker session, not allowed to block it.
The directory is owner-private 0700 and logs are 0600. Unsafe paths, symlinks,
hardlinks or preexisting permissive files are refused with a visible error;
there is no fallback to public temporary files. The app's single-instance lock
is required to avoid competing rotation by multiple workers. Debug output never
shares the framed protocol stdout. Direct worker CLI use with --advanced-debug
writes to its caller's stderr; the native adapter supplies the private bounded sink.
The same opt-in also writes delivery-debug.log (rotated to
delivery-debug.previous.log when debugging is enabled, capped near 1 MiB) in
that directory: paced-delivery metadata only — start/sent byte counts and
offsets, lease/send errors, finish results and which focus-guard check failed.
It never contains transcript text.
Hardware-free tests run through CTest, including fake-child cancellation, short injected warmup/request deadlines, duplicate/stale replies, malformed frames, missing/hash-mismatched model files and symlink refusal. Default production deadlines remain 120/60 seconds. Python tests never import actual model libraries, record a microphone, download assets or initialize OpenVR.