Always signal stack

Signal loss — documentation, packaging and dead constants

Drift

This repository has one commit. There is no history to consult, so the design record lives entirely in a README, an architecture document and a set of unusually detailed module docstrings. Two of those three describe a mechanism that was built, abandoned and replaced — and were never updated. This page is the reconciliation, item by item, with references.

doc references to a removed op 10 stale comment blocks 2 unreferenced constants 3 uncompiled Rust files 2 scripts that cannot pass 1

01 — The ghost

An operation that no longer exists

Every claim in this section reduces to one fact: the voice worker's dispatch has five branches and subscribe_ambient is not among them. Sending it produces {"ok": false, "error": "unknown op: subscribe_ambient"} and the connection closes.

Ten surviving references to a removed operation
LocationWhat it saysReality
README.md:68, 108, 118“New: dedicated reader thread + subscribe_ambient op”; the gateway “opens a long-lived subscribe_ambient connection per WebSocket”No reader thread, no op. A separate AmbientListener connects straight to the agent.
ARCHITECTURE.md §3.2, §3.3, glossarya full code listing of the subscribe_ambient handler, an _ambient_pump with a 1.2 s idle readline, and a _route_event fan-out functionNone of those functions exist in the tree.
gateway/server.py:2810–2835a 25-line comment block titled AMBIENT PUMP describing steps 1–4 of the removed designSits directly above _make_ambient_callback, which implements the replacement.
voice-daemon/jcode_voice_worker.py:78–87“Event types forwarded by the subscribe_ambient handler” and the AMBIENT_FORWARDED setThe set is defined and referenced nowhere in the file.
gateway/server.py:3004–3010_start_ambient_pump — the docstring itself admits “Name kept for backwards compatibility with call sites”correctly labelled

The last row is the exception that proves the rule: where the author touched the code, the comment was fixed. The drift is concentrated in the two documents nobody has to open in order to change behaviour.

The one test written for this feature cannot pass

scripts/test_ambient_drain.py is a four-step end-to-end smoke test: create a session, open a second socket and subscribe, drive a background subagent through jcode debug, then assert that at least three text_delta events arrive containing the expected phrase. Step two sends op=subscribe_ambient at line 82. It also waits for a "subscribed" acknowledgement event that no code path in the repository emits, and hard-codes /home/user/ paths for the worker socket, the agent socket and the jcode binary — while every other path in the tree uses /home/user/. The script prints FAIL.

Separately, ambient_listener.py:27 cites scripts/test_passive_subscriber.py as the empirical evidence for the design's central assumption. scripts/ contains one file, and that is not it.

02 — Packaging

What an installed wheel would not contain

pyproject.toml declares a console script, eight dependencies and a package-discovery rule. The rule includes always_assistant* and gateway*. It does not include voice-daemon — which cannot be a package anyway, because a hyphen is not a legal Python identifier. The gateway imports from it at runtime by mutating sys.path.

Packaging declaration versus runtime import reality Three columns. On the left, the pyproject declaration: two package include patterns and eight dependencies. In the middle, the working tree's top-level directories, with voice-daemon and asr_shim.py marked as outside the packaged set. On the right, the runtime import path: gateway server inserts the voice-daemon directory onto sys.path and imports AmbientListener from it, which succeeds from a source checkout and fails from an installed distribution. A lower band lists two modules imported by the code but absent from the dependency list. declared pyproject.toml name = "always" · version = "0.1.0" requires-python = ">=3.10" [tool.setuptools.packages.find] include = ["always_assistant*", "gateway*"] dependencies (8): aiortc · av · fastapi · httpx numpy · openwakeword · requests uvicorn[standard] [project.scripts] always-satellite-gateway = "gateway.server:main" on disk top-level Python ● always_assistant/ — packaged ● gateway/ — packaged ○ voice-daemon/ — not packaged a hyphen cannot be an import name ○ asr_shim.py — not packaged ○ desktop-delegate/ — not packaged ○ bin/, scripts/, systemd/ — not packaged package-data ships gateway/static/* only, so the fallback browser UI does survive at runtime gateway/server.py, lines 38–44 # Ambient listener lives in voice-daemon (sibling tree) — sys.path # bump so this import works from the deployed location. _VOICE_DAEMON_PATH = …parent.parent / "voice-daemon" _sys.path.insert(0, _VOICE_DAEMON_PATH) from ambient_listener import AmbientListener from a source checkout → resolves, path 4 works from an installed wheel → ModuleNotFoundError at import time the gateway does not start at all — it is a top-level import the shipped systemd unit runs uvicorn from a checkout, so this never bites in practice imported but never declared torch — asr_shim.py calls torch.hub.load() to fetch Silero VAD, and torch.from_numpy() per request PIL (pillow) — desktop-delegate/desktop_delegate.py:19 both live in whatever virtualenv the operator happened to build; neither is reachable through the declared dependency set This is not a defect so much as a statement of intent: the project is deployed by rsync, not by pip. The console script exists, but nothing invokes it.
Figure 13. Declaration, disk and runtime. The packaging metadata describes a library; the systemd unit runs uvicorn gateway.server:app with a WorkingDirectory pointing at a checkout, and the README's deploy procedure is six rsync and systemctl restart invocations over SSH. The two views were never reconciled because only one of them is ever used.
03 — Fossils

Code that is present and unreachable

A working personal system accumulates strata. Naming the layers is the difference between reading this repository quickly and reading it twice.

Unreachable or vestigial code, with evidence
ItemWhereEvidence it is dead
AMBIENT_FORWARDEDjcode_voice_worker.py:81–87defined; zero other references in the file
_AMBIENT_IDLE_FLUSH_S = 1.2gateway/server.py:2847five-line explanatory comment; one occurrence in the tree, the definition
--toolsets, --max-turnsjcode_voice_worker.py:969–970parsed by argparse, never read afterwards
src/message_notifications.rsjcode root src/207 lines, its own copy of the notification formatter without the subagent branch; no mod declares it
src/protocol/notifications.rsjcodesrc/protocol.rs is a single pub use line and never declares the module
Injection point “A”crates/jcode-protocol/src/lib.rs:865documented in a doc comment as “after stream”; the string is never emitted by any code path
reasoning_delta forwardingworker + listener event setsno such variant exists in ServerEvent
memory_worker.pyvoice-daemon/77 lines, no systemd unit, no importer, no CLI reference
pi_voice_worker.pyvoice-daemon/the Pi Agent RPC predecessor; zero references repository-wide
hermes_voice_worker.pyvoice-daemon/the Hermes CLI predecessor; referenced only by a dead path string
bin/hermes-voicebin/drives hermes-voice-daemon.service; the shipped unit is pi-voice-daemon.service
worker --socket defaultjcode_voice_worker.py:968defaults to pi-worker.sock; systemd and the gateway both use jcode-worker.sock

Two smaller numeric discrepancies, for completeness. ARCHITECTURE.md puts Kokoro on :8881 in two places; the shipped unit and the configuration default are both :8880. And both documents install the patched binary into a directory named 0.12.3-dev-always-features, while jcode/Cargo.toml declares version = "0.12.2". The architecture document also cites the injection banner at turn_streaming_mpsc.rs:1044; it is on line 1045.

04 — Status

What actually works

None of the above means the system does not run. It means the map is older than the territory. Judged only from the tree, this is the state of each subsystem:

Subsystem status, assessed from source only
SubsystemStateBasis
Capture, VAD, filter cascadeimplementedcomplete path; the VAD gate has a documented fail-closed default; three unit tests cover audio utilities
Foreground turn + streaming TTSimplementedthe bulk of the 74 tests in test_gateway.py exercise this path, including cancellation and replay
Ambient narration (Python)implementedAmbientListener is wired in and covered by tests using a FakeAmbientListener double
Background subagent (Rust)implemented, untestedno test constructs SubagentOutputMode::Background; the 256,000-byte inline branch has no coverage
End-to-end ambient proofbrokenscripts/test_ambient_drain.py targets a removed op and a foreign host's paths
Barge-in arbitrationimplementedsymmetric WebSocket and WebRTC handlers; both outcomes are explicit frames in the replay allow-list
Authenticationimplementedbearer, per-device and signed-ticket paths all live in gateway/server.py
WebRTC transportpartiala single one-shot offer endpoint with the full control vocabulary; no ICE trickle endpoint
Device registry / multi-devicepartialrecords, tokens, staleness expiry and a watchdog exist; one agent process serves all of them
Observabilityabsentseven per-turn floats and [STAGE] log lines; no exporter, no aggregation
Desktop delegatestandalonea complete EvoCUA computer-use CLI; grep finds no reference to it from the gateway, the worker or the assistant package
Level / waveform displayabsentone RMS scalar written to a <span>; no AnalyserNode or canvas anywhere
“The earlier attempt merged ambient listening INTO the chat client's connection pool … That hung because…”
voice-daemon/ambient_listener.py, line 29 — the only place in the repository where a rejected design is written down and explained

That docstring is the single most valuable artefact in the tree, and the reason the drift is recoverable at all. A reader who opens the two Markdown documents first will build an accurate mental model of a system that was deleted. A reader who opens ambient_listener.py first is told, in the first forty-five lines, what the problem was, what was tried, why it failed, and what replaced it. The documentation is stale; the code is not silent about it.